第二十课

在上一章里我们重点介绍了接口套件(Interface Kit),在 Haiku 中进行编程时我们还需要特别关注的是存储套件(Storage Kit)。该套件主要用于处理磁盘上的文件和文件夹。在这一节,我们将要对有关存储的内容进行一个快速的介绍。不过别担心,这一章不会很难。

存储套件概览

该套件由六种基本类型的类组成。

类名 描述
BfilePanel 一个 GUI 控件,它用于浏览文件系统和选择文件或者文件夹。
BReFilter 用于筛选 entry_ref 对象的类,它通常和 BFilePanel 联合使用。
Bquery 利用属性实现对文件系统的查询,Queries 仅能够在 BFS 卷中使用。
Bvolume 用于描述磁盘卷的类,该卷可以是硬盘分区,可移动设备,磁盘镜像,网络共享。
BVolumeRoster 该类提供了一些工具用以获取系统中所有可用卷的信息。
BDirectory BDirectory 是一个非常有用的类,它提供了一些方法用以读取文件夹中 的内容,创建文件,文件夹以及快捷式,而且可以用于找出特别的项目, 例如确定其是否存在。
BEntry 这是存储套件中最常用的类之一。用以表示文件夹中的一个项目,如文件或者 文件夹。一个 BEntry 可以告诉你该路径是否存在,并且删除,或者移动它 到同一个卷中的其他位置,还有其他的一些特性。
BEntryList 你可能不会直接使用 BentryList 类。它定义了一个用于读取 BEntry 对象列表 的接口,这也是它的派生类必须实现的。目前只有 BQuery 和 BDirectory 两个 对象用于处理此种情况。
BFile BFile 是另外一个常用的类。它代表了磁盘上的文件,提供了一些用于从文件中读取 数据和写入数据到文件中的功能。它提供了一个比 fread() 及其友元函数更有好的 接口,例如 BDirectory,它是一个 BNode 的子类,并且继承了 BNode 所有的 属性相关函数。
BNode 一个 BNode 类代表了一个文件的数据部分。它提供了一些用于文件锁定和处理文件 属性的函数。
BNodeInfo 尽管 BNode 可以完成所有 BNodeInfo 处理的内容,但是 BNodeInfo 提供了 一个用于一般属性任务的简单接口,例如取得文件类型等。需要特别注意的是 GetTrackerIcon() 静态函数,它用于获取在 Tracker 显示的图标。
BPath BPath 提供了简单的字符路径操作函数。它没有 BString 那么多花哨的技巧, 但是它在特殊路径处理方面尤其特别之处,例如,把相对路径转变为绝对路径和添 加项目名称。
BStatable 该类提供了一个用于处理 stat() 函数提供的消息的友好接口,包括项目类型 (文件,文件夹等),创建日期,修改日期,文件所有者等等。通常它并不会直 接使用。Bstatable 是一个完全的抽象类,主要用于创建有子类来实现的接口。 BNode 和 BEntry 都是 BStatable 的子类。
BSymLink 该类提供了一个用于处理 stat() 函数提供的消息的友好接口,包括项目类型 (文件,文件夹等),创建日期,修改日期,文件所有者等等。通常它并不会直 接使用。Bstatable 是一个完全的抽象类,主要用于创建有子类来实现的接口。 BNode 和 BEntry 都是 BStatable 的子类。
BAppFileInfo 如果你不是开发 IDE,文件浏览器,或者相似的东西的话,该类将不会经常用到。 它用于获取或者设置应用程序的相关信息,例如版本号,图标,支持类型等等。
BMimeType 该类型你也不会经常用到。它用于解析 MIME 信息串,获取文件类型数据库的 权限,和执行相关任务。它的主要任务是设置偏好程序的文件类型。
BResources 与 BAppFileInfo 相类似,你可能不会使用该类,除非你在编写一个特别的 程序,例如一个资源编辑器。它用于编程中源代码的装载,添加,和删除。
Node Monitor Node monitor 不是一个实际的类,而是一个存储套件的服务组件,主要用于 通知你文件系统中的更改,例如卷的安装与卸载,晚间的创建和删除,文件夹内 容的改变。

项目应用: ListDir

使用接口套件确实存在一个弊端:如果你使用了其中的一部分,那么你总是需要使用整个规范。例如,如果你希望从硬盘载入 BBitmap,你必须有一个有效地 BApplication,即使你可能不会用到它。但是存储套件不会有此限制。事实上,这里介绍的项目只是一个简单的终端程序,它仅用于本章的学习,但是它的代码风格与其他的 Haiku 程序是一致的。

该项目称为 listdir,它是 bash 命令 ls的一个简单的版本。在出命令行下给出相关路径,我们的程序将会列出该文件夹中的内容以及其下每一个项目的大小。我们将会通过列出主文件夹中项目的多少来显示任何主文件夹的“大小”。

Main.cpp

#include <Directory.h>
#include <Entry.h>
#include <Path.h>
#include <stdio.h>
#include <String.h>
// 最好使用全局的整型常数据成员来代替由#define定义的宏, 因为
// 常数据成员提供了强大的规范,不会像宏定义那样产生令人抓狂的错误
const uint16 BYTES_PER_KB = 1024;
const uint32 BYTES_PER_MB = 1048576;
const uint64 BYTES_PER_GB = 1099511627776ULL;
int                      ListDirectory(const entry_ref &dirRef);
BString          MakeSizeString(const uint64 &size);
int
main(int argc, char **argv)
{
    // We want to require one argument in addition to the program name when
    // invoked from the command line.
    if (argc != 2)
    {
        printf("Usage: listdir <path>\n");
        return 0;
    }
    // Here we'll do some sanity checks to make sure that the path we were given
    // actually exists and it's not a file.
    BEntry entry(argv[1]);
    if (!entry.Exists())
    {
        printf("%s does not exist\n",argv[1]);
        return 1;
    }
    if (!entry.IsDirectory())
    {
        printf("%s is not a directory\n",argv[1]);
        return 1;
    }
    // An entry_ref is a typedef'ed structure which points to a file, directory,
    // or symlink on disk. The entry must actually exist, but unlike a BFile or
    // BEntry, it doesn't use up a file handle.
    entry_ref ref;
    entry.GetRef(&ref);
    return ListDirectory(ref);
}
int
ListDirectory(const entry_ref &dirRef)
{
    // 该函数基本上完成了本程序所有的工作。
    BDirectory dir(&dirRef);
    if (dir.InitCheck() != B_OK)
    {
        printf("Couldn't read directory %s\n",dirRef.name);
        return 1;
    }
    // 我们所要做的第一件事是快速找出最长的文件名称的长度,
    // 在此基础上,我们才能够把所有文件以列表的形式对齐,
    // 在命令行界面中输出。
    int32 entryCount = 0;
    uint32 maxChars = 0;
    entry_ref ref;
    // 调用 Rewind() 函数把 BDirectory 的索引移动到列表的首部
    dir.Rewind();
    // 当 BDirectory 获取到列表中的最后一个文件时,
    // GetNextRef()函数将会返回 B_ERROR。
    while (dir.GetNextRef(&ref) == B_OK)
    {
        if (ref.name)
            maxChars = MAX(maxChars,strlen(ref.name));
    }
    maxChars++;
    char padding[maxChars];
    BEntry entry;
    dir.Rewind();
    // 这里我们调用了 GetNextEntry() 函数而不是 GetNextRef(),那是因为一个 BEntry
    // 将会允许我们获得每一个文件的信息,例如文件的大小。
    // 同时,由于它继承了 BStatable 的一些功能,我们可以
    // 仅需一个函数调用即可区分出文件和目录。
    while (dir.GetNextEntry(&entry) == B_OK)
    {
        char name[B_FILE_NAME_LENGTH];
        entry.GetName(name);
        BString formatString;
        formatString << "%s";
        unsigned int length = strlen(name);
        if (length < maxChars)
        {
            uint32 padLength = maxChars - length;
            memset(padding, ' ', padLength);
            padding[padLength - 1] = '\0';
            formatString << padding;
        }
        if (entry.IsDirectory())
        {
        // 在本程序中将会通过输出该文件夹中项目的多少来
        // 显示该文件夹的大小。
            BDirectory subdir(&entry);
            formatString << "\t" << subdir.CountEntries() << " items";
        }
        else
        {
            off_t fileSize;
            entry.GetSize(&fileSize);
            formatString << "\t" << MakeSizeString(fileSize);
        }
        formatString << "\n";
        printf(formatString.String(),name);
        entryCount++;
    }
    printf("%ld entries\n",entryCount);
    return 0;
}
BString
MakeSizeString(const uint64 &size)
{
    // 本函数把由 BEntry 的 GetSize() 方法提供的未加整理的字节数
    // 转换为一种更加友好的形式进行输出。
    BString sizeString;
    if (size < BYTES_PER_KB)
        sizeString << size << " bytes";
    else if (size < BYTES_PER_MB)
        sizeString << (float(size) / float(BYTES_PER_KB)) << " KB";
    else if (size < BYTES_PER_GB)
        sizeString << (float(size) / float(BYTES_PER_MB)) << " MB";
    else
        sizeString << (float(size) / float(BYTES_PER_GB)) << " GB";
    return sizeString;
}

深入了解

我们已经对接口套件和存储套件有了一定的了解。那么我们就有能力做一些东西了。你可能会找出一些以前的项目,然后检查其源码,看看自己可否对它进行修改和完善。如果你还没有尝试着创建自己的项目,那么你就该好好考虑一下啦。当然,很快的,接下来我们将会以更多的章节来介绍一些项目,在这些项目中,我们将会用到所有已经学过的编程内容。