自定义的打包文件
本文实现了一个自定义的打包格式,可以将一个目录下的所有文件打包成一个文件,并保持完整的目录结构。另外,该格式还支持分文件压缩,可以提取每个单独的文件而不必解包整个包文件。
首先,要保存一个目录下所有文件的内容是很简单的,将该目录下每个文件的内容依次连接起来形成一个大文件就可以了。当然,为了能区分各文件的范围,还必须记录每个文件的起始位置和长度。
但要记录完整的目录结构,就必须采用一定的算法记录目录信息。这里采用的方法很简单,就是对每个文件(或目录)记录其父目录。读取的时候,从每个文件开始上溯到根目录(就是包文件对应的目录)就知道每个文件的完整目录信息了。
为了说清楚问题,将文件和目录统一视为一个存储节点。则一个目录下所有的内容(包括所有的子文件夹和文件)构成了一棵树。每一个文件都对应树的叶子节点,目录则对应中间节点或叶子节点(空目录)。
为了保存整个树的信息,只要按一定顺序遍历树的每个节点并依次记录下来。然后同时记录下当前节点的父节点就可以了。
注意,这里有一个隐含的问题。就是遍历节点是必须满足一定的要求才可以根据上述信息重建目录。这个要求就是,必须在访问子节点之前访问该子节点的父节点。否则,重建目录的时候,可能会出现先生成一个子文件夹中的文件,后生成子文件夹的情况(这当然会导致创建文件失败)。
打包算法描述:定义一个结构StorageItem表示一个文件或目录,用该结构的数组描述一个目录下所有的内容,每一个文件或文件夹都对应数组的一个元素,该元素记录了为文件名,创建时间,文件大小等所有必须信息,另外,还记录了本元素对应的父节点元素在数组中的位置。只要保存该数组,就可以重现目录的所有详细信息。剩下的就是,按该数组的元素顺序,依次将对应的文件内容连接起来。
一个最简单的StorageItem结构可以如下定义:
typedef struct tagStorageItem
{
WIN32_FIND_DATA wfd;
int parent;
}StorageItem; //一个目录或文件
一个最简单的包文件可以如下定义: n + n个StorageItem结构 + n个存储项的内容.
当然,实际使用的时候,往往会有其它的要求,比如压缩,加密等等。因此,上面的结构还要再增加几个成员。另外,包文件也要定义的完整一些以便支持压缩和抽取独立文件等等应用。最终的存储结构和包文件定义如下:
typedef struct tagStorageItem
{
WIN32_FIND_DATA wfd;
DWORD offset; //起始位置
int parent;
int type;//0,copy. 1,zlib compress
DWORD size;
}StorageItem; //一个目录或文件
typedef struct tagPackFileHeader
{
char sign[8];
DWORD version;
DWORD FITPos; //file info table position.
DWORD FITType; //0,copy. 1,zlib compress.
DWORD FITcount;//FIT items count;
DWORD FITSize; //FIT size after compress.
DWORD FBPos; //file block position.
DWORD FBSize;
}PackFileHdr;
//pak文件 = 文件头 + 文件块 + 文件信息表.
//文件块 = n个文件内容依次连接.
//文件信息表 = n个StorageItem
//文件头按原始格式存储,其它各个部分都可压缩或加密.
扫描目录形成一个StorageItem的向量的代码如下:
typedef std::vector<StorageItem> FileVect;
BOOL ScanDir(LPCTSTR dir,int parent,FileVect &all)
{
WIN32_FIND_DATA wfd;
HANDLE hFind;
memset(&wfd,0,sizeof(WIN32_FIND_DATA));
hFind = FindFirstFile(dir,&wfd);
if(INVALID_HANDLE_VALUE == hFind)
return FALSE;
{//当前目录本身的记录项
StorageItem temp;
memcpy(&temp.wfd,&wfd,sizeof(WIN32_FIND_DATA));
temp.offset = 0;
temp.parent = parent;
temp.type = 0;
temp.size = 0;
all.push_back(temp);
FindClose(hFind);
parent = all.size()-1;
}
TCHAR find[MAX_PATH];
lstrcpy(find,dir);
lstrcat(find,_T("\\*.*"));
memset(&wfd,0,sizeof(WIN32_FIND_DATA));
hFind = FindFirstFile(find,&wfd);
if(INVALID_HANDLE_VALUE == hFind)
return FALSE;
while(1)
{
if(lstrcmp(wfd.cFileName,".")&&lstrcmp(wfd.cFileName,".."))
{
if(FILE_ATTRIBUTE_DIRECTORY & wfd.dwFileAttributes)//文件夹
{
TCHAR SubDir[MAX_PATH];
lstrcpy(SubDir,dir);
lstrcat(SubDir,"\\");
lstrcat(SubDir,wfd.cFileName);
ScanDir(SubDir,parent,all);
}
else//文件
{
StorageItem temp;
memcpy(&temp.wfd,&wfd,sizeof(WIN32_FIND_DATA));
temp.offset = 0;
temp.parent = parent;
temp.type = 0;
temp.size = 0;
all.push_back(temp);
}
}
memset(&wfd,0,sizeof(WIN32_FIND_DATA));
if(!FindNextFile(hFind,&wfd))
break;
}
FindClose(hFind);
return TRUE;
}
注意,上面的算法中,处理任何一个目录或子目录的时候,首先都保存自己的节点项,然后才处理目录中的文件。这样可以保证解包的时候一定是先碰到目录项,然后才碰到目录下的文件项。
完整代码见附件,其中使用了zlib进行了某些类型的文件压缩。