搜索文件

 

一、搜索文件

  1. 要搜索的文件可能就是文件名,像pdflatex.fmt。除文件名,搜索须要带上一个指示文件类型的参数,像FileType::FMT。
  2. 因为给的就是文件名,得知道在哪几个目录搜。这几个目录,是通过文件类型得到的,MikTex约定了哪类型文件只能放在哪些目录,这目录称为路径模式(pathPattern)。
  3. 对硬件上某个目录,底下多的可能有上万个文件,怎么判断这文件名是否在里面呢?——建立文件名数据库(fndb)。初始化时,针对几个目录,生成该目录(rootDirectory)下,由所有文件构的一个哈希表,一个rootDirectory对应一个哈希表。对哈希表中单元key->value,可认为key就是没目录符的文件名,像babel-russian.tex、pdflatex.fmt。value则含有该文件全路径中、剔除了rootDirectory的相对目录。
  4. fndb中预建立的rootDirectory个数毕竟是有限,可能也就是一、两个。pathPattern则可能是很多个。但是,会尽量保证rootDirectory是所有pathPattern的前缀。
  5. fndb->Search(PathName(fileName), it->ToString(), ..)。此函数在某个文件名数据库中根据目标文件名(fileName)和路径模式(it->ToString())搜索文件,返回匹配的文件路径元数据。过程大致是这样的:每个哈希表key是文件名,目标文件名直接和这些文件名比较,得到文件名一样的那几条哈希记录(可能几条,文件名一样,但目对路径不一样),该记录写有此文件剔除了rootDirectory的相对目录relativeDirectory,于是“rootDirectory+relativeDirectory+目标文件名”已可能是个最终文件全路径。
  6. 通过文件数据数库找到的全路径,只是说在fndb中存在,可能实际已经被删了,CheckCandidate就用于判断合路径是否真的存在硬盘上。

一个示例

  1. 要搜索目标文件:pdflatex.fmt,文件类型:FileType::FMT。
  2. 根据文件类型FMT,知道有条路径模式(pathPattern)是C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox\miktex/data/le/pdftex。
  3. 初始化时建立文件名数据库。当中有个rootDirectory=C:/ddksample/apps-res/app-launcher/tflites/miktex/sandbox的做啥希表,表中有条记录,key是pdflatex.fmt,value中相对路径(relativeDirectory)是miktex/data/le。
  4. 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找到一条全路径。
  5. 全路径(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)。成功条件(满足其一即可):

  1. 模式耗尽且路径耗尽: *pathPattern == 0 && *path == 0
  2. 模式只剩递归符且路径耗尽: strcmp(pathPattern, "//") == 0 && *path == 0
  3. 模式只剩单斜杠且路径耗尽: 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();
}

 

二、

全部评论: 0

    写评论: