硬盘坏了,一气之下写了个恢复程序

硬盘坏了,一气之下写了个恢复程序

师傅拯救无望

硬盘已经寄过去超过一周了,一问竟然是还没开始弄???

2023-03-24-14-15-16.png

再过一周,上来就问我分几个区?我要恢复哪些数据?我要恢复的数据在哪个位置?

2023-03-24-14-18-50.png

2023-03-24-14-19-30.png

2023-03-24-14-20-05.png

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

自救之路

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

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

  • 硬件损坏,对坏的盘进行修复
  • 误删或逻辑错误等,文件扫描修复
  • 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 的数据目录一般保存在这些地方:

参考: https://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(
      /.*?\/\/\/(.*$)/,
      `$1`
    )
    // 原文件存储目录
    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 的文件管理器风格,大概如下。

image.png

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

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

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

2023-03-24-15-09-25.png

由于界面是在浏览器里的,需要自动打开,浏览器与系统交互需要一个接口,所以我们使用 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

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

2023-03-24-15-22-23.png

所有代码位于:

建议三连,以备不时之需。/手动狗头

posted @ 2023-03-24 15:34  程序媛李李李李蕾  阅读(135)  评论(0编辑  收藏  举报