65.9K
CodeProject 正在变化。 阅读更多。
Home

用于支持 Windows® 登录/关机屏幕轮播的符号链接轮播实用程序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (8投票s)

2016 年 12 月 15 日

CPOL

15分钟阅读

viewsIcon

14138

downloadIcon

155

厌倦了千篇一律的 Windows 登录/关机屏幕?我使用一个符号链接轮播实用程序来循环切换一系列背景图片。

背景

我不喜欢我的 PC 厂商为登录/关机屏幕提供的背景图片。  而且,由于我拥有海量的桌面背景图片,我开始研究如何才能在我的桌面背景图片之间循环切换,用作登录/关机屏幕。  一些不错的来源提供了关于一次性更改的信息(例如 http://thedesktopteam.com/raphael/sccm-2012-cutomizing-windows-lock-screen/)。  剩下要做的就是编写一个实用程序,该实用程序可以根据某个事件或计划进行计划,以更改登录/关机图形。  我决定不复制和重命名文件,而是使用符号链接,这会导致操作系统将所有对符号链接的文件访问重定向到链接指定的另一个文件(有关详细信息,请参阅 https://msdn.microsoft.com/en-us/library/windows/desktop/aa365680(v=vs.85).aspx)。

注意事项

  • 请自行承担风险使用。  并非我认为有风险,但我在使用不兼容的文件作为登录/关机图形时发现了一些令人烦恼的问题。
    • 在 Windows 系统目录(例如 %windir%\System32\oobe\Info\)方面,您可能需要使用高级属性对话框更改目录的所有权,并为运行 LinkRotate 实用程序的用户名和系统帐户设置完全权限。
    • Windows 7 只能处理 256KB 或更小的背景图形。  较大的文件要么无法加载,要么系统将恢复为 Windows 提供的默认背景图形。
    • 登录/关机屏幕会以拉伸以适应模式显示背景图形;这可能会破坏图片的审美。
    • 更改 Windows 主题可能会撤销您的登录/关机屏幕注册表设置,并关闭自定义登录/关机背景图形(请参阅 https://blogs.technet.microsoft.com/win7/2011/02/16/the-value-of-oembackground-registry-key-resets-back-to-0-after-theme-gets-changed/)。
  • 下面介绍的 LinkRotation 实用程序基于 Windows 控制台命令 `dir /n` 的一致输出。
  • 是的,LinkRotation 实用程序可能不仅限于循环切换登录/关机背景图形。
  • 是的,我提前道歉,我的代码风格确实很紧凑……如果需要,可以使用免费的代码美化器。

为什么选择 C++

可能实际上存在 C# 或 JAVA 中的 Windows API,允许创建、内省和操作符号链接,但我没有找到(如果您知道任何,请在下方评论或发布一篇文章)。  WIN32 API 有一种创建符号链接的方法,但我找不到发现或更改链接目标的方法。  我在 C++ 中找到可以使用的是 _popen()。  将 `dir /n` 作为命令参数传递给 _popen(),API 调用,可以解析从命令返回的输出流,以提取 Windows 文件系统中符号链接的目标值。

方法

LinkRotate 实用程序设计为一个控制台应用程序,以便它可以通过注册表或任务计划程序运行,而无需用户界面。

  1. 确定当前符号链接的目标。
  2. 根据命令行中提供的目录路径和过滤器进行筛选,以捕获每个目录中的文件(或符号链接文件)列表。
  3. 保存遇到的第一个文件,以备在搜索中后续文件均不匹配要轮播的链接目标时使用。
  4. 筛选指定路径中的文件,查找与当前符号链接目标匹配的文件。
    1. 如果找到,
      1. 将符号链接设置为指向搜索中找到的下一个文件,
      2. 或在搜索结束时指向已保存的第一个文件。
      3. 完成,...停止搜索/筛选,全部退出。

用法

lr.exe <符号链接文件名> [/p:<目标路径> [/f:<文件名过滤器> [...]] [...]] [/r|/recursion] [/s|/subdirectories] [/q|/quiet] [/v|/verbose]

<符号链接文件名>
第一个未标记的命令行参数指定文件系统中的符号链接,该链接将被修改或轮播到搜索中的下一个文件。
/p <目标路径> -或- /p:<目标路径>
可以指定零个或多个目录路径来搜索符号链接候选文件。
如果未指定,轮播列表将默认为符号链接文件本身所在的目录。
/f <文件名过滤器> -或- /f:<文件名过滤器>
在任何路径选项后面都可以指定零个或多个通配符过滤器。
如果未指定过滤器,则考虑目录路径中的所有文件。
/r -或- /recursion
在找到的文件列表中递归遍历子目录。等同于 /s
/s -或- /subdirectories
在文件列表中包含子目录。等同于 /r
/q -或- /quiet
禁止控制台输出。
/v -或- /verbose
详细显示控制台输出。

登录/关机符号链接

有关如何指定和启用单个特定登录/关机背景的完整说明,请参阅 http://thedesktopteam.com/raphael/sccm-2012-cutomizing-windows-lock-screen/。  在我自己的计算机上,我发现以下两个文件之一是将被替换为所需背景图形文件的目标。

  • 要么是 %windir%\System32\oobe\background.bmp
  • 要么是 %windir%\System32\oobe\Info\backgrounds\backgroundDefault.jpg

我没有将单个图形文件复制并重命名为上述名称之一,而是使用 Windows 的 mklink 命令创建了一个初始符号链接(有关此命令的更多信息,请参阅 http://www.computerhope.com/mklink.htm)。  该链接指向我想要用作背景轮播起点的实际图形文件。

级联符号链接以解决不兼容文件问题

事实证明,在 Windows 7 上,某些图形文件无法用作登录/关机背景。

  • 大于 256KB 的文件将无法加载。
  • 在我的一台系统上,分辨率高于系统屏幕分辨率的文件不兼容。
  • 文件在显示时会被拉伸以适应,这可能不是“不兼容”,但拉伸或收缩或像素化后的美感可能非常差,以至于无法使用。
  • 即使在大小、尺寸和文件格式应该兼容的情况下,也有一些文件根本无法在登录/关机屏幕上显示。  我对此类不兼容性无法解释。

我的桌面背景图形缓存中的大多数文件根据上述标准都不能用作登录/关机背景图形。  我不想删除或复制我的宝藏,并且愉快地发现符号链接可以指向其他符号链接;操作系统将遍历链接的级联以达到最终目标文件。  因此,我创建了指向我期望兼容的一小部分文件的链接,后来删除了那些最终被证明不兼容的文件的次级符号链接。

换句话说,对于登录/关机背景,我的 Windows 指向一个背景图形文件,该文件本身是一个符号链接。  然后,我使用 LinkRotation 实用程序将背景链接文件的目标从一个符号链接文件组成的短期列表池中轮播,这些符号链接文件随后链接到实际的背景图形文件。

需要管理员权限

下一个障碍是 Windows OS 的 mklink 命令本身,因此 LinkRotate 实用程序需要以管理员权限运行。  对我来说,完成此任务的最佳方法是在 Visual Studio 构建的链接步骤配置中设置此标志。

<rant> Code Project vs. GitHub </rant>

提前为这次小小的咆哮道歉。  当我第一次接触 codeproject.com 时,我误解了该网站的一些信息。  我最初天真地开始在 codeproject.com 上发布,期望它会成为一个促进代码和想法的社区。  别误会我的意思,我绝对重视和欣赏这个社区及其多样化的想法和知识。  它是获取专业知识的首选网站。

但我发现它更多的是一个供个人发表意见和展示单一专业知识的网站;而不是一个促进“代码项目”协作的网站。  它确实有一个很好的开源许可证(CPOL),但没有为发布的代码项目提供存储库支持。  如果代码项目被修改并重新发布,则不会通知先前感兴趣的成员。(我是否可以推荐一个 codeproject.com - GitHub 合作伙伴关系?)

另一方面,GitHub 确实促进了代码项目的这些活动。  因此,尽管我从在 codeproject.com 上发布这篇文章和源代码中受益,尽管我从该网站上其他人的贡献中受益匪浅,但该代码项目最最新的源代码和协作将在 GitHub 上找到。

代码审查第一部分 - 目录列表捕获(请根据您的兴趣继续)

全局枚举

控制台应用程序通过全局枚举支持详细程度和递归子目录处理的差异。

enum verbosity { quiet = 0, normal, verbose };
enum verbosity _verbosity(verbosity::normal);

enum recursion { off = 0, on };
enum recursion _recursion(recursion::off);

oList 模板类

这是一个标准的单向链表类和模板类的融合,可以应用于任何带有推荐的私有 pNext 指针的节点类。  我用它来收集和迭代目录路径规范、文件过滤器规范和文件规范。

template<typename T> class oList
{
private:
  T * pHead;
  T * pTail;

public:
  oList()
  {
    pHead = NULL;
    pTail = NULL;
  }

  ~oList()
  {
    T * n0;
    T * n = pHead;

    while (n != NULL)
    {
      n0 = n;
      n = n->pNext;
      delete n0;
    }
  }

  void Add(T * n)
  {
    if (pHead == NULL)
    {
      pHead = n;
      pTail = n;
    }

    else
    {
      pTail->pNext = n;
      pTail = n;
    }
  }

  int Count() const
  {
    int ret = 0;
    T * n = pHead;
    while (n != NULL)
    {
      n = n->pNext;
      ++ret;
    }

    return ret;
  }

  T * const operator[] (int index) const
  {
    if (index < 0) return NULL;

    T * n = pHead;
    while (n != NULL && index-- > 0) n = n->pNext;

    return n;
  }
};

oFilterNode 类

class oFilterNode
{
  oFilterNode * pNext;
  char * pfilter;

public:
  oFilterNode(const char * const filter_)
  {
    pNext = NULL;
    pfilter = (filter_ != NULL) ? _strdup(filter_) : NULL;
  }

  ~oFilterNode()
  {
    if (pfilter != NULL) free(pfilter);
    pNext = NULL;
  }

  const char * const Filter() const { return pfilter; }

  friend class oList<oFilterNode>
};

*注意节点类末尾的 friend class oList<oFilterNode>; 声明。  这使得 oList 模板类能够访问 oFilterNodepNext 私有成员,即使它不在节点类的继承层次结构中。

oPathNode 类

class oPathNode
{
  oPathNode * pNext;
  char *  ppath;
  oList<oFilterNode> *  pfilters;

public:

  oPathNode(const char * const path_)
  {
    pNext = NULL;
    ppath = (path_ != NULL) ? _strdup(path_) : NULL;
    pfilters = new oList<oFilterNode>();
  }

  ~oPathNode()
  {
    if (ppath != NULL) free(ppath);
    delete pfilters;
    pNext = NULL;
  }

  oList<oFilterNode> * Filters() { return pfilters; }
  const char * const Path() { return ppath; }

  friend class oList<oPathNode>;
};

*注意节点类末尾的 friend class oList<oPathNode>; 声明。  这使得 oList 模板类能够访问 oPathNodepNext 私有成员,即使它不在节点类的继承层次结构中。

oFileNode 类

首先是概述,然后是工作方法 CreateFileList() 的检查。

class oFileNode
{
  oFileNode *  pNext;

  char *  ppath;
  char *  pdate;
  char *  ptime;
  char *  ptype;
  char *  pname;
  char *  plink;

  char *  pfullpath;

  oFileNode(
   const char * const path_,
   const char * const date_,
   const char * const time_,
   const char * const type_,
   const char * const name_,
   const char * const link_)
  {
    ppath = (path_ != NULL) ? _strdup(path_) : NULL;
    pdate = (date_ != NULL) ? _strdup(date_) : NULL;
    ptime = (time_ != NULL) ? _strdup(time_) : NULL;
    ptype = (type_ != NULL) ? _strdup(type_) : NULL;
    pname = (name_ != NULL) ? _strdup(name_) : NULL;
    plink = (link_ != NULL) ? _strdup(link_) : NULL;

    pfullpath = NULL;
    pNext = NULL;
  }

  ~oFileNode()
  {
    if (ppath != NULL) free(ppath);
    if (pdate != NULL) free(pdate);
    if (ptime != NULL) free(ptime);
    if (ptype != NULL) free(ptype);
    if (pname != NULL) free(pname);
    if (plink != NULL) free(plink);
    if (pfullpath != NULL) free(pfullpath);
    pNext = NULL; 
  }

public:

  static oList<oFileNode> * CreateFileList(oList<oPathNode> * _paths) {...}

  const char * const FullPath() 
  {
    if (pfullpath == NULL)
    {
      size_t  len = strlen(ppath) + strlen(pname) + 3;
      pfullpath = (char *)malloc(len);
      strcpy_s(pfullpath, len, ppath);
      if (pfullpath[strlen(pfullpath) - 1] != '\\') strcat_s(pfullpath, len, "\\");
      strcat_s(pfullpath, len, pname);
    }

    return pfullpath;
  }

  const char * const Link() const { return plink; }
  const char * const Name() const { return pname; }
  const char * const Path() const { return ppath; }
  const char * const Path(const char * const path_)
  {
    if (ppath != NULL) free(ppath);
    ppath = (path_ != NULL) ? _strdup(path_) : NULL;
    return ppath;
  }

  const char * const Type() const { return ptype; }

  friend class oList<oFileNode>;
};

*注意节点类末尾的 friend class oList<oFileNode>; 声明。  这使得 oList 模板类能够访问 oFileNodepNext 私有成员,即使它不在节点类的继承层次结构中。

此外,为了方便起见,该类允许重置 Path() 成员,并在需要时构造生成的 FullPath()

使用静态 oFileNode::CreateFileList() 方法捕获目录或目录的内容

oFileNode::CreateFileList() 解析从 _popen() 调用返回的每一行,并将文件或符号链接的详细信息提取到 oFileNodes 的 oList 中。

标准图形目录列表的输出如下所示

'oobe' 目录列表中符号链接的输出如下所示

为了支持递归下降到子目录,此函数必须在路径列表中操作,该列表在处理列表中已有的另一个路径的条目时,可能会在其末尾添加其他目录路径。  这使得循环从固定的 for() 循环 变得复杂,需要改为“检查是否完成”的 while() 循环。  此外,要解析的路径可能还有额外的过滤器列表要循环遍历。  下面的逻辑展开了这两个迭代器,并将它们一起处理。  它依赖于 oList 方法 Add() 严格地追加到 oList 的末尾,并且 oList operator[] 当索引越界时返回 NULL

static oList<oFileNode> * CreateFileList(oList<oPathNode> * _paths)
{
  oList<oFileNode> *  ret = new oList<oFileNode>();
  const char *        cmd0 = "dir /n ";
  oPathNode *         path_node = NULL;

  int  i = 0, j = 0;
  while ((path_node = (*_paths)[i]) != NULL)
  {
    const char *  path = path_node->Path();
    oList<oFilterNode> *  _filters = path_node->Filters();
    const char *  filter = ((*_filters)[j] != NULL) ? (*_filters)[j]->Filter() : NULL;

    if (filter == NULL)
    {
      ++i;
      if (j > 0)
      {
        j = 0;
        continue;
      }
    }

    else ++j;

一旦识别出要处理的路径和可选的配对文件过滤器,就可以构建传递给 _popen() 的命令的命令行。

  size_t tmp_len = 0;

  if (path != NULL && (tmp_len = strlen(path)) > 0)
  {
    size_t  cmd_len = strlen(cmd0) + tmp_len + ((filter != NULL) ? strlen(filter) : 0) + 3;
    char *  cmd = new char[cmd_len];

    strcpy_s(cmd, cmd_len, cmd0);
    strcat_s(cmd, cmd_len, path);
    if (filter != NULL)
    {
      if (cmd[strlen(cmd) - 1] != '\\' && filter[0] != '\\') strcat_s(cmd, cmd_len, "\\");
      strcat_s(cmd, cmd_len, filter);
    }

    FILE * cmd_out = _popen(cmd, "rt");

_popen() 返回一个文件流,其中包含提交的 Windows OS 命令的输出(请参阅上面的 `dir /n` 输出)。  下一个任务是解析返回的信息以捕获文件信息。  这就是我在没有 API 的情况下,找到捕获符号链接目录条目目标信息的方法。 为实现这一点,我设置了一个简单的级联状态机来指导解析 `dir /n <path>[<filter>]` 命令的输出。

    if (cmd_out != NULL)
    {
      enum parse_mode {
       startline,
       skipline,
       capturedate,
       skiptotime,
       capturetime,
       skiptotype,
       capturetype,
       capturesize,
       skiptoname,
       capturename,
       capturelink
      } cmd_parse_mode = parse_mode::startline;

      char  odate[25];
      char  otime[25];
      char  otype[25];
      char  osize[25];
      char  oname[FILENAME_MAX + 1];
      char  olink[FILENAME_MAX + 1];

      int  c = fgetc(cmd_out);
      while (feof(cmd_out) == 0)
      {

如果在输出流中意外发现行尾,则放弃当前行的处理,并开始处理下一行输出。  否则,如果在期望 EOL 的阶段发现行尾,并且执行继续到 else 块以完成 EOL 处理。

通常,每个处理阶段,以捕获字段,是通过

  • 检查阶段的进入条件,
  • 清除阶段的存储并从初始进入条件(例如,我们已读取的输出流中的字段的第一个字符)初始化,
  • 从输出流中捕获字段的其余部分(一次一个字符),
  • 然后跳过字段之间的空格,直到满足下一个字段的初始条件。

这是捕获 `dir /n` 命令输出中的日期字段的代码。

        if ((c == '\r' || c == '\n') && cmd_parse_mode != parse_mode::capturename)
          cmd_parse_mode = parse_mode::startline;

        else switch (cmd_parse_mode)
        {
        case parse_mode::startline:
          if (isdigit(c))
          {
            memset(odate, 0, sizeof(odate));
            odate[0] = (char)c;
            cmd_parse_mode = parse_mode::capturedate;
          }

          else cmd_parse_mode = parse_mode::skipline;
          break;

        case parse_mode::skipline:
          break;

        case parse_mode::capturedate:
          if (!isspace(c))
          {
            if ((tmp_len = strlen(odate)) < sizeof(odate)) odate[tmp_len] = (char)c;
          }
          else cmd_parse_mode = parse_mode::skiptotime;
          break;

捕获日期字段后,捕获时间字段。

        case parse_mode::skiptotime:
          if (!isspace(c))
          {
            memset(otime, 0, sizeof(otime));
            otime[0] = (char)c;
            cmd_parse_mode = parse_mode::capturetime;
          }
          break;

        case parse_mode::capturetime:
          if (!isspace(c))
          {
            if ((tmp_len = strlen(otime)) < sizeof(otime)) otime[tmp_len] = (char)c;
          }
          else cmd_parse_mode = parse_mode::skiptotype;
          break;

然后是类型字段(如果存在)。  如果不存在,则是大小字段。  在 Windows 上,目录条目要么有类型,要么有大小。

        case parse_mode::skiptotype:
          if (c == '<')
          {
            memset(otype, 0, sizeof(otype));
            memset(osize, 0, sizeof(osize));
            cmd_parse_mode = parse_mode::capturetype;
          }

          else if (!isspace(c))
          {
            memset(otype, 0, sizeof(otype));
            memset(osize, 0,sizeof(osize));
            osize[0] = (char)c;
            cmd_parse_mode = parse_mode::capturesize;
          }
          break;

        case parse_mode::capturetype:
          if (c == '>') cmd_parse_mode = parse_mode::skiptoname;
          else
          {
            if ((tmp_len = strlen(otype)) < sizeof(otype)) otype[tmp_len] = (char)c;
          }
          break;

        case parse_mode::capturesize:
          if (!isspace(c))
          {
            if ((tmp_len = strlen(osize)) < sizeof(osize)) osize[strlen(osize)] = (char)c;
          }
          else cmd_parse_mode = parse_mode::skiptoname;
          break;

然后是名称字段,该字段以行尾字符或方括号字符 ('[') 结束,这是链接目标字段的初始条件。

捕获名称和链接(如果适用)字段并修剪掉尾部空格后,将一个条目添加到累积的 oList<oFileNode> 列表中,该列表最终将被返回。

如果捕获的条目代表一个目录(除了 '.' 当前目录和 '..' 前目录),并且启用了递归,则已识别目录的完整路径将添加到正在处理的目录列表的末尾,以及当前目录路径使用的所有过滤器(如果有)。  这个新添加到列表末尾的条目保证会被处理,即使它是在后期添加的。

        case parse_mode::skiptoname:
          if (!isspace(c))
          {
            memset(oname, 0, sizeof(oname));
            memset(olink, 0, sizeof(olink));
            oname[0] = (char)c;
            cmd_parse_mode = parse_mode::capturename;
          }
          break;

        case parse_mode::capturename:
          if (c == '\r' || c == '\n')
          {
            while ((tmp_len = strlen(oname) - 1) >= 0 && isspace(oname[tmp_len])) oname[tmp_len] = '\0';
            oFileNode * fn = new oFileNode(path, odate, otime, otype, oname, olink);

            if (_recursion == recursion::on
             && fn->Type() != NULL
             && strcmp(fn->Type(), "DIR") == 0
             && fn->Name() != NULL
             && fn->Name()[0] != '.')
            {
              size_t path_len_ = strlen(path) + strlen(fn->Name()) + 3;
              char * recurse_path_ = new char[path_len_];

              strcpy_s(recurse_path_, path_len_, path);
              if (path[strlen(path) - 1] != '\\') strcat_s(recurse_path_, path_len_, "\\");
              strcat_s(recurse_path_, path_len_, fn->Name());

              oPathNode * rpath_ = new oPathNode(recurse_path_);
              for (int k = 0; k < _filters->Count(); ++k)
              {
                rpath_->Filters()->Add(new oFilterNode((*_filters)[k]->Filter()));
              }
              _paths->Add(rpath_);

              delete recurse_path_;
            }

            ret->Add(fn);
            cmd_parse_mode = parse_mode::startline;
          }

          else if (c == '[')
          {
            while ((tmp_len = strlen(oname) - 1) >= 0 && isspace(oname[tmp_len])) oname[tmp_len] = '\0';
            if (otype[0] != '\0') cmd_parse_mode = parse_mode::capturelink;
            else
            {
              ret->Add(new oFileNode(path, odate, otime, otype, oname, olink));
              cmd_parse_mode = parse_mode::skipline;
            }
          }

          else if ((tmp_len = strlen(oname)) < sizeof(oname) - 1) oname[tmp_len] = (char)c;
          break;

        case parse_mode::capturelink:
          if (c == ']')
          {
            ret->Add(new oFileNode(path, odate, otime, otype, oname, olink));
            cmd_parse_mode = parse_mode::skipline;
          }

          else if ((tmp_len = strlen(olink)) < sizeof(olink) - 1) olink[tmp_len] = (char)c;
          break;
        }

在分阶段处理结束时,从控制台命令的输出中读取下一个字符。  当没有更多字符时,关闭控制台并删除为指定控制台应运行的 OS 命令而创建的字符串。

作为边缘情况清理,设置一个仅返回一个条目的无过滤器列表的路径描述符。  这通常发生在指定的路径引用现有文件而不是目录时,并且由 main() 用于验证正在轮播其目标的符号链接文件。

最后,将结果列表返回给调用者。

        c = fgetc(cmd_out);

        if (_verbosity >= verbosity::verbose) _fputchar(c);
      }

      _pclose(cmd_out);
    }

    delete cmd;

    if (ret->Count() == 1 && filter == NULL)
    {
      const char * pch = strrchr(path, '\\');
      if (pch == NULL) (*ret)[0]->Path(".\\");
      else if (strcmp((pch + 1),(*ret)[0]->Name()) == 0)
      {
        char * ppath = _strdup(path);
        if (ppath != NULL)
        {
          char * ppch = strrchr(ppath, '\\');
          if (ppch != NULL)
          {
            *ppch = '\0';
            (*ret)[0]->Path(ppath);
          }

          free(ppath);
        }
      }
    }
  }

  return ret;
}

休息时间到了?

我带您看了很多代码;您确定想看剩下的吗?  或者也许是时候 休息一下, 喝一杯您选择的提神饮料。  在您放松的时候,可以看看 NASA 的每日图片廊 或 NASA 的每日天文图片;那里总是充满地球和空间科学的视觉发现宝藏,也是背景图形的良好来源。  在您等待的时候,这里有一个小样本

代码审查第二部分 - 链接轮播的 main()(请根据您的兴趣继续)

命令行参数处理

LinkRotation Utility 的 main() 例程的第一部分处理命令行参数。  由于它是一个轻量级实用程序,只有几个参数,所以我采取了一些自由。  我通过单个字母来识别命令行标志,但会吞噬提供的整个单词(例如,/p、-path 和 /penuchle 都匹配为路径标志)。  尽管未指定,但我允许使用空格分隔和冒号分隔(':')的参数。

由于某些参数会影响其他参数的处理,主要是递归,我采用了两遍的方法来首先处理先前的参数。

int main(int argc, char ** argv)
{
  char const *        _link = NULL;
  oList<oPathNode> *  _paths = new oList<oPathNode>();

  char const usage_prompt[] = "\nUsage:\n\t%s <symbolic link filename> [/p:<target path> [/f:<filename filter> [...]] [...]] [/r|/recursion] [/s|/subdirectories] [/q|/quiet] [/v|/verbose]\n";

  int    i, j, cnt;
  char * pch;

  // Parse precedent command line arguments first.
  for (i = 1; i < argc; ++i)
  {
    switch (argv[i][0])
    {
    case '-':
    case '/':
      switch (argv[i][1])
      {
      case '?':
      case 'h': case 'H':
        printf_s(usage_prompt, argv[0]);
        return 0;

      case 'r': case 'R':
      case 's': case 'S':
        _recursion = recursion::on;
        break;

      case 'q': case 'Q':
        _verbosity = verbosity::quiet;
        break;

      case 'v': case 'V':
        _verbosity = verbosity::verbose;
        break;
      }
      break;
    }
  }

在处理完先前的参数后,提取路径和过滤器参数,以及用于操作的符号链接文件所需的参数。  请注意,解析器同时处理空格分隔和冒号分隔的参数。

找到过滤器参数后,会将它们添加到最近解析的路径参数中。  会将一个选项附加到过滤器中,以排除返回的被搜索值中的目录。  如果启用了递归,还会添加一个额外的过滤器来仅返回目录。  当使用过滤器时,这两种添加都必不可少,以将文件和目录的处理分开。  (当不使用过滤器时,则不需要额外的处理。)

  // Re-parse for non-precedent command line arguments.
  for (i = 1; i < argc; ++i)
  {
    const char * err_msg = "unrecognized parameter";
    switch (argv[i][0])
    {
    case '-':
    case '/':
      switch (argv[i][1])
      {
      case '?':
      case 'h': case 'H':
      case 'q': case 'Q':
      case 'r': case 'R':
      case 's': case 'S':
      case 'v': case 'V':
        //Precedent paramaters that have already been parsed.
        break;

      case 'p': case 'P':
        if ((pch = strchr(argv[i], ':')) != NULL)
        {
          _paths->Add(new oPathNode(++pch));
        }

        else if (i + 1 < argc && argv[i + 1][0] != '-' && argv[i + 1][0] != '/')
        {
          _paths->Add(new oPathNode(argv[++i]));
        }

        else
        {
          err_msg = "unparsable path parameter";
          goto cmd_parse_error;
        }
        break;

      case 'f': case 'F':
        if ((pch = strchr(argv[i], ':')) != NULL) ++pch;
        else if (i + 1 < argc && argv[i+1][0] != '-' && argv[i+1][0] != '/') pch = argv[++i];

        else
        {
          err_msg = "unparsable filter parameter";
          goto cmd_parse_error;
        }

        if ((cnt = _paths->Count()) > 0)
        {
          const char * const _dir_filter = " /A:D";
          const char * const _sans_dir_filter = " /A:-D";
          oPathNode * p = (*_paths)[cnt - 1];

          if (_recursion == recursion::on && p->Filters()->Count() == 0 )
          {
            p->Filters()->Add(new oFilterNode(_dir_filter));
          }

          size_t len_ = strlen(pch) + strlen(_sans_dir_filter) + 3;
          char * pfilter_ = new char[len_];

          strcpy_s(pfilter_, len_, pch);
          strcat_s(pfilter_, len_, _sans_dir_filter);
          p->Filters()->Add(new oFilterNode(pfilter_));

          delete pfilter_;
        }

        else
        {
          err_msg = "filter parameter found without preceeding path parameter";
          goto cmd_parse_error;
        }

        break;

      default:
cmd_parse_error:
        perror("\nbad command line parameter:\n");
        perror(err_msg);
        perror("\n");
        if (_verbosity >= verbosity::normal) printf_s(usage_prompt, argv[0]);
        return -1;
      }
      break;

    default:
      if (_link != NULL) goto cmd_parse_error;
      _link = argv[i];
      break;
    }
  }

接下来,执行一些检查来设置并确保已满足操作先决条件。  这包括运行预期将轮播其目标的符号链接文件,通过 oFileNode::CreateFileList() 处理器,以便可以提取目标链接,以及检查其作为符号链接文件的存在性。  请记住,我没有找到一个 API 可以为我做这件事。

当仅指定符号链接文件时,我将在指定的符号链接文件所在的目录中处理链接轮播。

为求清晰起见,我将开始将目标被轮播的符号链接文件称为“主文件”。

  if (_link == NULL)
  {
    perror("\nmissing symbolic link command line parameter\n");
    if (_verbosity >= verbosity::normal) printf_s(usage_prompt, argv[0]);
    return -2;
  }

  oList<oPathNode> *    pPrincipal = new oList<oPathNode>();
  pPrincipal->Add(new oPathNode(_link));
  oList<oFileNode> *    pPrincipalList = oFileNode::CreateFileList(pPrincipal);

  if (pPrincipalList->Count() > 1)
  {
    perror("\nsymbolic link cannot specify multiple files\n");
    if (_verbosity >= verbosity::normal) printf_s(usage_prompt, argv[0]);
    return (-3);
  }

  oFileNode * const    pPrincipalFile = (*pPrincipalList)[0];

  if (pPrincipalFile != NULL && pPrincipalFile->Link() == NULL)
  {
    // A link is expected to facilitate the rotation.
    // This also protects against the loss of an existing file.
    perror("\ncannot replace a real file with a symbolic link\n");
    if (_verbosity >= verbosity::normal) printf_s(usage_prompt, argv[0]);
    return (-4);
  }

  if (_paths->Count() == 0)
  {
    char * pdir = _strdup(_link);
    pch = strrchr(pdir, '\\');
    if (pch != NULL) *pch = '\0';
    else if (strcpy_s(pdir,strlen(_link),".\\") != 0)
    {
      perror("\ncould not set directory path for link candidates\n");
      if (_verbosity >= verbosity::normal) printf_s(usage_prompt, argv[0]);
      return(-5);
    }

    _paths->Add(new oPathNode(pdir));
    free(pdir);
  }

处理捕获的目录条目

在验证了初始约束后,然后捕获要处理的完整文件列表。  目标是找到“主文件”中当前链接目标过去的那个文件或符号链接文件。  必须设置一个保护措施,以防“主文件”本身出现在正在处理的文件列表中。

作为一种故障安全措施,pnewtarget 被设置为第一个可以用作轮播新目标的文件。  一旦在正在处理的列表中找到“主文件”的当前目标,pnewtarget 就会被设置为下一个潜在目标。  如果没有下一个潜在目标,则使用故障安全目标。  这就是轮播工作方式或重置方式,即使当前目标永远找不到,或者仅在最后一步找到,因为该过程正在退出搜索循环。

  if (_verbosity > verbosity::normal) printf_s("\n\n");

  char *  pnewtarget = NULL;
  bool    bprincipallinkfound = false;
  bool    bexitloops = false;

  oList<oFileNode> * pCandidateList = oFileNode::CreateFileList(_paths);
  cnt = pCandidateList->Count();
  for (j = 0; !bexitloops && j < cnt; ++j)
  {
    if (_verbosity > verbosity::normal)
    {
      printf_s("%s [%s] ?= :%s: %s [%s]\n",
          pPrincipalFile->Name(),
          pPrincipalFile->Link(),
          (*pCandidateList)[j]->Type(),
          (*pCandidateList)[j]->Name(),
          (*pCandidateList)[j]->Link());
    }

    if (((*pCandidateList)[j]->Type() == NULL || _stricmp((*pCandidateList)[j]->Type(), "DIR") !=0)
        && _stricmp(pPrincipalFile->Name(), (*pCandidateList)[j]->Name()) != 0)
    {
      if (_stricmp(pPrincipalFile->Link(), (*pCandidateList)[j]->Name()) == 0
         || _stricmp(pPrincipalFile->Link(), (*pCandidateList)[j]->FullPath()) == 0)
      {
        bprincipallinkfound = true;
      }

      else
      {
        if (bprincipallinkfound)
        {
          if (pnewtarget != NULL) free(pnewtarget);
          pnewtarget = NULL;
          bexitloops = true;
        }

        if (pnewtarget == NULL) pnewtarget = _strdup((*pCandidateList)[j]->FullPath());
      }
    }
  }
  delete pCandidateList;

将链接设置为轮播中的下一个目标

一旦确定了“主文件”的新目标,就会为 _popen() API 构建一个新的命令行,该 API 使用适当的参数调用 Windows OS mklink 命令。  由于符号链接文件不能使用 mklink 命令进行编辑,因此必须首先将其删除(这里使用 _unlink() ,具有讽刺意味的是)。

轮播完成后,进行清理并退出。

  if (pnewtarget != NULL) 
  {
    const char * const cmd0 = "mklink ";
    size_t cmd_len = strlen(cmd0) + strlen(pPrincipalFile->FullPath()) + strlen(pnewtarget) + 6;
    char * cmd = (char *)malloc(cmd_len);
    FILE * cmd_out = NULL;

    strcpy_s(cmd, cmd_len, cmd0);
    strcat_s(cmd, cmd_len, "\"");
    strcat_s(cmd, cmd_len, pPrincipalFile->FullPath());
    strcat_s(cmd, cmd_len, "\" \"");
    strcat_s(cmd, cmd_len, pnewtarget);
    strcat_s(cmd, cmd_len, "\"");

    if (_verbosity > verbosity::normal) printf_s("\n\ndeleting: %s\n", pPrincipalFile->FullPath());
    if (_unlink(pPrincipalFile->FullPath()) == -1)
    {
      char buf[280];
      strerror_s(buf, errno);
      perror(buf);
    }

    if (_verbosity > verbosity::normal) printf_s(command_prompt, cmd);
    if ((cmd_out = _popen(cmd,"rt")) != NULL)
    {
      int c;
      while (!feof(cmd_out))
      {
        c = fgetc(cmd_out);
        if (_verbosity > verbosity::normal) _fputchar(c);
      }
      _pclose(cmd_out);
    }
    free(cmd);
    free(pnewtarget);
  }

  else perror("\n\nLink not rotated.  No candidate files found or processed.\n\n");

  delete pPrincipalList;
  delete pPrincipal;
  delete _paths;
  return 0;
}

历史

2016.12.15 - 初始提交

© . All rights reserved.