硬盘坏了,一气之下写了个恢复程序
硬盘坏了,一气之下写了个恢复程序
师傅拯救无望
硬盘已经寄过去超过一周了,一问竟然是还没开始弄
???
再过一周,上来就问我分几个区?我要恢复哪些数据?我要恢复的数据在哪个位置?
那好吧,既然给了钱师傅也都放弃了,我也没什么好寄托希望的了。况且经过这三个星期的缓解,心情已经平复了很多,就像时光,回不来了就是回不来了。
自救之路
在把硬盘寄过去的时间里,等待师傅的修复结果的时间里,我并没有闲着(在摸鱼)。
经过调研,数据恢复方法通常有:
- 硬件损坏,对坏的盘进行修复
- 误删或逻辑错误等,文件扫描修复
- 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 的文件管理器风格,大概如下。
如果还原的文件最新的那份不是我想要的,我还能根据时间线进行对比和选择
理论上这里应该要做一个像 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
这就是恢复后的文件在硬盘里的样子啦:
所有代码位于:
建议三连,以备不时之需。/手动狗头