硬盤壞了,一氣之下寫了個恢復程序

硬盤壞了,一氣之下寫了個恢復程序

師傅拯救無望

硬盤已經寄過去超過一週了,一問竟然是還沒開始弄???

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

所有代碼位於:

建議三連,以備不時之需。/手動狗頭

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章