一、搜索文件
- 要搜索的文件可能就是文件名,像pdflatex.fmt。除文件名,搜索须要带上一个指示文件类型的参数,像FileType::FMT。
- 因为给的就是文件名,得知道在哪几个目录搜。这几个目录,是通过文件类型得到的,MikTex约定了哪类型文件只能放在哪些目录,这目录称为路径模式(pathPattern)。
- 对硬件上某个目录,底下多的可能有上万个文件,怎么判断这文件名是否在里面呢?——建立文件名数据库(fndb)。初始化时,针对几个目录,生成该目录(rootDirectory)下,由所有文件构的一个哈希表,一个rootDirectory对应一个哈希表。对哈希表中单元key->value,可认为key就是没目录符的文件名,像babel-russian.tex、pdflatex.fmt。value则含有该文件全路径中、剔除了rootDirectory的相对目录。
- fndb中预建立的rootDirectory个数毕竟是有限,可能也就是一、两个。pathPattern则可能是很多个。但是,会尽量保证rootDirectory是所有pathPattern的前缀。
- fndb->Search(PathName(fileName), it->ToString(), ..)。此函数在某个文件名数据库中根据目标文件名(fileName)和路径模式(it->ToString())搜索文件,返回匹配的文件路径元数据。过程大致是这样的:每个哈希表key是文件名,目标文件名直接和这些文件名比较,得到文件名一样的那几条哈希记录(可能几条,文件名一样,但目对路径不一样),该记录写有此文件剔除了rootDirectory的相对目录relativeDirectory,于是“rootDirectory+relativeDirectory+目标文件名”已可能是个最终文件全路径。
- 通过文件数据数库找到的全路径,只是说在fndb中存在,可能实际已经被删了,CheckCandidate就用于判断合路径是否真的存在硬盘上。
一个示例
- 要搜索目标文件:pdflatex.fmt,文件类型:FileType::FMT。
- 根据文件类型FMT,知道有条路径模式(pathPattern)是C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex。
- 初始化时建立文件名数据库。当中有个rootDirectory=C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox的做啥希表,表中有条记录,key是pdflatex.fmt,value中相对路径(relativeDirectory)是miktex/data/le。
- fndb->Search("pdflatex.fmt", C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex, ..),第二个参数pathPattrn剔除该哈希表前缀后是miktex/data/le。哈希表中,key=pdflatex.fmt时,相对路径(relativeDirectory)是miktex/data/le,这满足“模式耗尽且路径耗尽: *pathPattern == 0 && *path == 0”,Search找到一条全路径。
- 全路径(rootDirectory+relativeDirectory+目标文件名):C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox + miktex/data/le + pdflatex.fmt
接下以要搜索pdflatex.fmt为例,看搜索过程。
1.1 TeXMFApp::OpenMemoryDumpFile
fileName。要搜索文件:pdflatex.fmt。
renew。pdflatex.fmt是个必须存在文件,找不到的话,须要生成,但renew意意和这个新建无关。它指的是,即使有,也要强制生成。renew = false: 使用现有的文件(如果存在且有效)。renew = true: 强制重新创建文件,即使已存在。
bool TeXMFApp::OpenMemoryDumpFile(const PathName& fileName_, FILE** ppFile, void* pBuf, size_t size, bool renew)
{
...
Session::FindFileOptionSet findFileOptions;pdflatex.fmt是个必须存在文件,找不到的话,须要生成。Create就是用于标记该没有或没有,就生成。
findFileOptions += Session::FindFileOption::Create;
if (renew)
{renew = true: 强制重新创建文件,即使已存在。
findFileOptions += Session::FindFileOption::Renew; } if (!session->FindFile(fileName.ToString(), GetMemoryDumpFileType(), findFileOptions, path)) ... }
调用session->FindFile时参数。
- [IN]fileName:pdflatex.fmt。
- [IN]filetype:FileType::FMT,
- [IN]options:Session::FindFileOption::Create || Session::FindFileOption::Renew(如果renew=true)。
- [OUT]result:用于存放搜到的该文件全路径。
1.2 SessionImpl::FindFile
SessionImpl内有好多种不同参数版本的FileFile。
bool SessionImpl::FindFile(const string& fileName, FileType fileType, FindFileOptionSet options, PathName& result)
{
MIKTEX_ASSERT(!options[FindFileOption::All]);
MIKTEX_ASSERT(!options[FindFileOption::All]);
LocateOptions locateOptions;
locateOptions.create = options[FindFileOption::Create];
locateOptions.fileType = fileType;
locateOptions.renew = options[FindFileOption::Renew];
locateOptions.searchFileSystem = options[FindFileOption::SearchFileSystem];options转换到locateOptions。options[FindFileOption::SearchFileSystem]是0,因而locateOptions.searchFileSystem是false。
if (auto locateResult = Locate(fileName, locateOptions); !locateResult.pathNames.empty())
{pathNames类型是std::vector<MiKTeX::Util::PathName>。可以猜测,此次Locate可能找到多个目录,它们底下都有pdflatex.fmt。
那它们是要按优先级排列的,优先级最高的排在[0]。
result = locateResult.pathNames[0]; return true; } return false; }
调用Locate时参数。
- [IN]givenFileName:pdflatex.fmt。
- [IN]filetype:FileType::FMT,
- [IN, OUT]options:create=true, fileType = FileType::FMT, renew = 来自FindFileOption::Renew。
1.3 SessionImpl::Locate
Locate返回值不再是bool,而是该文件的全路径。
LocateResult MIKTEXTHISCALL SessionImpl::Locate(const string& givenFileName, const LocateOptions& options)
{
...
vector<PathName> pathNames;
if (options.fileType == FileType::EXE) {
pathNames.push_back(initStartupConfig.userDataRoot / fileName);
found = true;
} else if (options.fileType == FileType::None) {
...
}
else
{fileType是FileType::FMT,进这个入口
found = FindFileByType(fileName, options.fileType, options.all, options.searchFileSystem, options.create,
options.renew, pathNames, options.callback);
}pathNames可能存在同名单元,有同名的就取一个后,它就是返回值。
... }
调用FindFileByType时参数。
- [IN]fileName:pdflatex.fmt。
- [IN]filetype:FileType::FMT。
- [IN]all:false。
- [IN]searchFileSystem:false。
- [IN]create:true。
- [IN]renew:false。
- [OUT]result:用于存放搜到的该文件全路径。
- [IN]callback:nullptr。
1.4 SessionImpl::FindFileByType
fileName: pdftex.ini, all来自options.all, searchFileSystem来自options.searchFileSystem,值都是false。会生成一个文件名数组,fileNamesToTry,size是2。[0]是pdftex.ini.tex,[1]是要搜索的pdftex.ini。
搜索策略。先是从fndb搜索;如搜索不到options.searchFileSystem是true,从FileSystem中找。
bool SessionImpl::FindFileByType(const string& fileName, FileType fileType, bool all, bool searchFileSystem,
bool create, bool renew, vector<PathName>& result, IFindFileCallback* callback)
{
...
// construct the search vector根据文件类型返回对应的目录搜索模式,从而让后面知道,在哪些目录中搜索特定类型的文件。也就是说,MikTex约定了哪类型文件只能放在哪些目录。针对FileType::FMT,这里得到3个目录。
- pathPatterns[0]: .
- pathPatterns[1]: C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex
- pathPatterns[2]: C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/
vector<PathName> pathPatterns = GetDirectoryPatterns(fileType); // get the file type information
返回文件类型的元数据信息,包含文件扩展名、处理方式等详细信息。
const InternalFileTypeInfo* fti = GetInternalFileTypeInfo(fileType); MIKTEX_ASSERT(fti != nullptr); ... // try it with the given file name fileNamesToTry.push_back(PathName(fileName));
对示例,fileNamesToTry只有一个单元。
- fileNamesToTry[0]: pdflatex.fmt
for (const PathName& fn : fileNamesToTry)
{
if (FindFileInDirectories(fn.ToString(), pathPatterns, all, true, false, result, callback) && !all)
{
return true;
}
}
// second round: don't use the FNDB如果searchFileSystem=true,要进行第二轮,不使用FNDB。
if (searchFileSystem)
{
for (const PathName& fn : fileNamesToTry)
{
if (FindFileInDirectories(fn.ToString(), pathPatterns, all, false, true, result, callback) && !all)
{
return true;
}
}
}
if (create)
{该文件是必须存在的,文件没搜到时,会生成一个。对pdflatex.fmt,要执行miktex_makefmt.exe,在这里等待,直到miktex_makefmt.exe执行完。
} return !result.empty(); }
在GetDirectoryPatterns,换做要搜索的是pdftex.ini,其文件类型是FileType::TEX,那pathPattrns会是以下样子。
pathPatterns[0]: . pathPatterns[1]: C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\tex/plain// pathPatterns[2]: \\MiKTeX\]MPM[\tex/plain// pathPatterns[3]: C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\tex/generic// pathPatterns[4]: \\MiKTeX\]MPM[\tex/generic// pathPatterns[5]: C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\tex// pathPatterns[6]: \\MiKTeX\]MPM[\tex//
相应的,后面fileNamesToTry。
fileNamesToTry[0]: pdftex.ini.tex fileNamesToTry[1]: pdftex.ini
对FileType::TEX类型,文件扩展名有*.tex,因而多了pdftex.ini.tex。
调用FindFileInDirectories时参数。
- [IN]fileName:pdflatex.fmt。
- [IN]pathPatterns
- pathPatterns[0]: .
- pathPatterns[1]: C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex
- pathPatterns[2]: C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/
- [IN]all:false。
- [IN]useFndb:true。
- [IN]searchFileSystem:false。
- [OUT]result:用于存放搜到的该文件全路径。
- [IN]callback:
1.5 SessionImpl::FindFileInDirectories
函数名中的Directories,指的是根据文件类型得到的特定目录,此函数会依次在这些目录中搜文件。
useFndb和searchFileSystem中至少有一个得是true。
fndb是文件名数据库缩写,这个数据库的顶层变量是:std::vector<RootDirectoryInternals> rootDirectories。对每单元,可认为是个key->value映射,key是目录名,value是这目录下存在的文件。此时有两个单元。
- [0].rootDirectory: C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox
- [1].rootDirectory: \\MiKTeX\]MPM[
bool SessionImpl::FindFileInDirectories(const string& fileName, const vector<PathName>& pathPatterns,
bool all, bool useFndb, bool searchFileSystem, vector<PathName>& result, IFindFileCallback* callback)
{
...
if (useFndb)
{
for (vector<PathName>::const_iterator it = pathPatterns.begin(); (!found || all) && it != pathPatterns.end(); ++it)
{
...
shared_ptr<FileNameDatabase> fndb = GetFileNameDatabase(it->GetData());
if (fndb != nullptr)
{GetFileNameDatabase判断是否在哪个库单元的依据是,该单元key,即止录,是it->GetData()的前缀。
it->GetData()即某个pattern,当pattern是C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex,
库单元C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox是它的前缀,因而此时会得到这个库单元,变指fndb指向这个单元。
vector<Fndb::Record> records;
bool foundInFndb = fndb->Search(PathName(fileName), it->ToString(), all, records);
// we must release the FNDB handle since CheckCandidate() might request an unload of the FNDB
fndb = nullptr;
if (foundInFndb)
{
for (int idx = 0; idx < records.size(); ++idx)
{- records[idx].path:C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex\pdflatex.fmt
- records[idx].fileNameInfo.c_str()
在文件数据数库中找文件records[idx].path。当这文件可能是在fndb中存在,实际已经被删了,CheckCandidate就用于判断是文件是否真的存在硬盘上。
if (CheckCandidate(records[idx].path, records[idx].fileNameInfo.c_str(), callback))
{硬盘有这文件,那个文件可以为搜索结果之一。
found = true;
result.push_back(records[idx].path);
}
}
}
} else {即使useFndb是true,还是会用SearchFileSystem搜索的
vector<PathName> paths;
if (SearchFileSystem(fileName, it->GetData(), all, paths, callback))
{
found = true;
result.insert(result.end(), paths.begin(), paths.end());
}
}
}
}
//
if (found || !searchFileSystem)
{
return found;
}
..
}调用Search时参数。
- [IN]relativePath:pdflatex.fmt。
- [IN]pathPattern:C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex。
- [IN]all:false。
- [OUT]result:存放搜索到的文件路径元数据。当中主要是全路径名。示例:C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex\pdflatex.fmt
1.6 FileNameDatabase::Search
在文件名数据库中根据相对路径和路径模式搜索文件记录,返回匹配的文件路径元数据。
bool FileNameDatabase::Search(const PathName& relativePath, const string& pathPattern_,
bool all, vector<Fndb::Record>& result)
{
...fileName是relativePath中的文件名部分。此时relativePath是pdflatex.fmt,没有目录,fileName就是pdflatex.fmt。
PathName fileName = relativePath.GetFileName(); ...
fileNames是文件名哈希表,对哈希表单元key,可认为就是没目录符的文件,像babel-russian.tex、pdflatex.fmt,
没有用std::map,可能是这个表比较大吧,像此时表中有18162个单元/文件。
pair<FileNameHashTable::const_iterator, FileNameHashTable::const_iterator> range = fileNames.equal_range(MakeKey(fileName)); ...
到此处,变量pathPattern值是miktex/data/le/pdftex。来自参数pathPattern_剔除库单元key()的剩下部分。
库单元key(rootDirectory):C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox
PathName comparablePathPattern(pathPattern); comparablePathPattern.TransformForComparison();
comparablePathPattern值:miktex/data/le/pdftex
for (FileNameHashTable::const_iterator it = range.first; it != range.second; ++it)
{
PathName relativeDirectory;
relativeDirectory = it->second.GetDirectory();relativeDirectory值: miktex/data/le/pdftex
if (Match(comparablePathPattern.GetData(), PathName(relativeDirectory).TransformForComparison().GetData()))
{Match功能是路径模式匹配。功能是检查路径path(参数2)是否匹配模式pathPattern(参数1)。成功条件(满足其一即可):
- 模式耗尽且路径耗尽: *pathPattern == 0 && *path == 0
- 模式只剩递归符且路径耗尽: strcmp(pathPattern, "//") == 0 && *path == 0
- 模式只剩单斜杠且路径耗尽: strcmp(pathPattern, "/") == 0 && *path == 0
这里满足条件1。
PathName path; path = rootDirectory; path /= relativeDirectory.ToString(); path /= fileName.ToString();
path值:C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex\pdflatex.fmt
trace_fndb->WriteLine("core", fmt::format(T_("found: {0} ({1})"), Q_(path), Q_(it->second.GetInfo())));
result.push_back({ path, it->second.GetInfo() });
if (!all)
{
break;
}
}
}
return !result.empty();
}
二、