那好吧,既然给了钱师傅也都放弃了,我也没什么好寄托希望的了。况且经过这三个星期的缓解,心情已经平复了很多,就像时光,回不来了就是回不来了。

在把硬盘寄过去的时间里,等待师傅的修复结果的时间里,我并没有闲着(在摸鱼)。

经过调研,数据恢复方法通常有:

  • 硬件损坏,对坏的盘进行修复
  • 误删或逻辑错误等,文件扫描修复
  • git 重置恢复
  • 很明显,这些都不适用于我现在的场景。因为师傅能不能修好是未知的,我只是数据盘没了,系统盘还在。由于 vscode 的数据目录空间占比较小,就没有搬迁到数据盘里,这刚好可以为恢复代码提供了可能。

    这是因为新版 vscode 有一个 时间线 功能,这个时间线数据是默认存储在用户目录下的。

    我从 C:/Users/love/AppData/Roaming/Code/User/History 目录中确实找到了很多名为 entries.json 的文件,结构如下:

    // 配置版本 "version": 1, // 原来文件所在位置 "resource": "file:///d%3A/git2/cloudcmd/.madrun.mjs", // 文件历史 "entries": [ // 历史文件存储的名称 "id": "YFRn.mjs", "source": "工作区编辑", // 修改的时间 "timestamp": 1656583915880 "id": "Vfen.mjs", "timestamp": 1656585664751

    通过上面的文件大概可以看到,每一个时间点的文件都保存在另一个随机命名的文件里。而网上的方法基本都是自己一个个手动到目录里去根据最新的 id 去找对应的文件内容,然后创建文件并把内容复制出来。

    这个过程恢复一两个文件还好,但我这可是要恢复整个 git 工作区,大概有几十个项目上千个文件。

    这时候当然是在网上找找有没有什么 vscode 数据恢复 相关的工具,很遗憾找了大半天都没有找到。

    气死我了,一气之下就自己写个!

    恢复程序开发步骤

    毕竟只要数据在磁盘上,无非就是一个文件读取操作的问题,还要拿在这水文章,见谅见谅。

    首先考虑需求:

  • 我要实现一个自动扫描 vscode 数据目录
  • 然后以原始的目录结构还原出来,不需要我自己去创建文件夹和文件
  • 如果还原的文件最新的那份不是我想要的,我还能根据时间线进行对比和选择
  • 扫描出来有N个项目时,我可以指定只还原某此项目
  • 我可以搜索文件、目录名或文件内容进行还原
  • 为了方便,我还要一个看起来不太丑的操作界面
  • 大概就上面这些吧。

    然后考虑实现:

    我要实现一个自动扫描 vscode 数据目录

    要的就是我自己连数据目录和恢复地址也不需要填写,就能自动恢复的那种。那么就让程序来自动查找数据目录。经过调研,各版本的 vscode 的数据目录一般保存在这些地方:

    参考: stackoverflow.com/a/72610691

      - win -- C:\Users\Mark\AppData\Roaming\Code\User\History
      - win -- C:\Users\Mark\AppData\Roaming\Code - Insiders\User\History
      - /home/USER/.config/VSCodium/User/History/
      - C:\Users\USER\AppData\Roaming\VSCodium\User\History
    

    大概有上面这些路径,当然不排除使用者故意把默认位置修改掉这种边缘情况,或者使用者就只想扫描某个数据目录的情况,所以我也要支持手动输入目录:

      let { historyPath, toDir } = req.body
      const homeDir = os.userInfo().homedir
      const pathList = [
        historyPath,
        `${homeDir}/AppData/Roaming/Code/User/History/`,
        `${homeDir}/AppData/Roaming/Code - Insiders/User/History/`,
        `${homeDir}/AppData/Roaming/VSCodium/User/History`,
        `${homeDir}/.config/VSCodium/User/History/`,
      historyPath = (() => {
        return pathList.find((path) => path && fs.existsSync(path))
      toDir = toDir || normalize(`${process.cwd()}/re-store/`)
    

    然后以原始的目录结构还原出来……

    这就需要解析扫描到的时间线文件 entries.json 了。我们先把解析结果放到一个 list 中,以下是一个完整的解析方法。

    然后再把列表转换为树型,与硬盘上的状态对应起来,这样便于调试数据和可视化。

    function scan({ historyPath, toDir } = {}) {
      const gitRoot = `${historyPath}/**/entries.json`
      fs.existsSync(toDir) === false && fs.mkdirSync(toDir, { recursive: true })
      const globbyList = globby.sync([gitRoot], {})
      let fileList = globbyList.map((file) => {
        const data = require(file)
        const dir = path.parse(file).dir
        // entries.json 地址
        data.from = file
        data.fromDir = dir
        // 原文件地址
        data.resource = decodeURIComponent(data.resource).replace(
          /.*?\/\/\/(.*$)/,
        // 原文件存储目录
        data.resourceDir = path.parse(data.resource).dir
        // 恢复后的完整地址
        data.rresource = `${toDir}/${data.resource.replace(/:\//g, `/`)}`
        // 恢复后的目录
        data.rresourceDir = `${toDir}/${path
          .parse(data.resource)
          .dir.replace(/:\//g, `/`)}`
        const newItem = [...data.entries].pop()
        // 创建文件所在目录
        fs.mkdirSync(data.rresourceDir, { recursive: true })
        const binary = fs.readFileSync(`${dir}/${newItem.id}`, {
          encoding: `binary`,
        fs.writeFileSync(data.rresource, binary, { encoding: `binary` })
        return data
      const tree = pathToTree(fileList, { key: `resource` })
      return tree
    

    为了方便,我还要一个看起来不太丑的操作界面

    我们要把文件树的形式展示出来,还要方便切换。后面决定使用 macos 的文件管理器风格,大概如下。

    如果还原的文件最新的那份不是我想要的,我还能根据时间线进行对比和选择

    理论上这里应该要做一个像 vscode 对比文件那样,有代码高亮功能,并且把有差异的字符高亮出来。

    实际上,这个需求得加钱。

    由于界面是在浏览器里的,需要自动打开,浏览器与系统交互需要一个接口,所以我们使用 opener 来自动打开浏览器。

    使用 get-port 来自动生成接口服务的端口,避免使用时出现占用。

      const opener = require(`opener`)
      const { portNumbers, default: getPort } = await import(`get-port`)
      const port = await getPort({ port: portNumbers(3000, 3100) })
      const server = express()
      server.listen(port, `0.0.0.0`, () => {
        const link = `http://127.0.0.1:${port}`
        opener(link)
    

    封装成工具,我为人人

    理论上我根本不需要什么 UI 界面,也不需要配置,因为我的文件都恢复出来了我还花时间去搞毛线?

    实际上,万一别人也有这个恢复文件的需要呢?那么他只要运行下面这条命令代码就能立刻恢复到当前目录啦!

    npx vscode-file-recovery
    

    这就是恢复后的文件在硬盘里的样子啦:

    所有代码位于:

  • github github.com/wll8/vscode…
  • 建议收藏,以备不时之需。/手动狗头

  • 私信