用Electron開發企業網盤(二)--分片下載


  書接上文,背景見:https://www.cnblogs.com/shawnyung/p/10060119.html

HTTP請求頭  Range

  請求資源的部分內容(不包括響應頭的大小),單位是byte,即字節,從0開始。

  如果服務器能夠正常響應的話,服務器會返回 206 Partial Content 的狀態碼及說明.

  如果不能處理這種Range的話,就會返回整個資源以及響應狀態碼為 200 OK 。

Range請求頭格式

Range: bytes=start-end

響應頭

Conent-Length

  表示這次服務器響應數據的字節數

一、思路整理

  用過迅雷等下載工具會發現:文件在下載過程中,會生成.downloading后綴和.downloading.cfg后綴的兩個文件。.downloading后綴的文件跟文件已下載的大小是一致的,而.downloading.cfg后綴的文件特別小。當文件下載完成后,.downloading后綴及.downloading.cfg文件均不存在,只保留下載完成的文件。

  通過網上了解知道,cfg文件大多是配置文件。那么可以 推測出:.downloading文件是下載的臨時文件,接收下載文件流。而.downloading.cfg是下載的配置文件,保存文件下載的相關信息。

  配合斷點續傳的需求,梳理出分片下載的方案:文件下載,首先判斷當前目錄有沒有已下載的斷點文件。若有,則創建一個'append'的文件流,通過.downloading.cfg文件讀取已下載分片的相關信息,續傳下載;若無,則創建一個新文件流,指定請求文件的部分內容(分片)。傳輸過程中,將文件流寫入.downloading文件,並同步更新.downloading.cfg文件,記錄下載文件的相關信息及分片信息。每一片傳輸完成,判斷服務器相應數據的字節是否小於分片字節數。若是,表示為最后一個分片,文件已下載完成,將.downloading文件重命名為原文件名並刪除.downloading.cfg文件。

二、分解任務

  將任務分解成幾個子任務:

1、遞歸創建文件夾。

2、判斷當前目錄有沒有已下載的斷點文件,創建文件流。

3、設定HTTP請求頭Range,分片請求文件url。

 4、更新.downloading.cfg文件。

 5、文件下載完成,重命名.downloading文件並刪除.downloading.cfg文件。

1、遞歸創建文件夾

  完整路徑為“D:/tmp/新建文件夾/002.docx”之類的文件在下載時需要先一級一級創建文件夾。借助Node的fs及path模塊,完成遞歸創建文件夾任務。

const fs = require("fs")
const path = require("path")

const mkdirs = (dirname, callback, errback) => {
  fs.stat(dirname, (err, stats) => {
    if (err) {
      mkdirs(path.dirname(dirname), () => {
        fs.mkdir(dirname, callback)
      }, errback)
    } else {
        if (stats.isDirectory()) {
            callback()
        } else {
              errback()
        }
    }
  })
}

2、父級文件夾創建好后,判斷當前目錄有沒有已下載的斷點文件,創建文件流。

  fs.createWriteStream返回WriteSteam對象,用於創建文件寫入流。

fs.createWriteStream(path[, options])

還是借助Node的fs模塊的stat方法,檢測當前目錄有沒有.downloading文件。若有,則創建一個flags為'a'的文件流;若無,則創建一個默認的文件流。

let statDir = function (flag) {
    fs.stat(file.path + '.downloading', function (err, stats) {
      if (flag) {
        if (err) {
          contents.send('download-error', file.path)
          stream.end()
          return
        }
      } else {
        stream = !err ? fs.createWriteStream(file.path + '.downloading', {flags: 'a'}) :
        fs.createWriteStream(file.path + '.downloading')
        streams.push(stream)
        if (!err) {
          receivedBytes += stats.size
        }
      }
      func()
    })
  }

3、設定HTTP請求頭Range,分片請求文件url。

net

使用Chromium的原生網絡庫發出HTTP / HTTPS請求

  net 模塊是一個發送 HTTP(S) 請求的客戶端API。 它類似於Node.js的HTTP 和 HTTPS 模塊 ,但它使用的是Chromium原生網絡庫來替代Node.js的實現,提供更好的網絡代理支持。

  receivedBytes為.downloading臨時文件已下載的文件流大小,chunkSize為分片大小。所以每個分片的請求內容為receivedBytes至receivedBytes + chunkSize - 1。每個分片下載完成后,更新receivedBytes大小。

const request = net.request(url)
    let start = receivedBytes
    let end = receivedBytes + chunkSize - 1
    request.setHeader('Range', 'bytes=' + start + '-' + end)
    request.on('response', (response) => {
      response.on('data', chunk => {
        if (response.statusCode == 206) {
          try {
            stream.write(chunk)
          } catch(e) {}
        }
      })

      let contentLength = response.headers['content-length'][0]
      response.on('end', () => {
        receivedBytes += parseInt(contentLength)
    }

4、更新.downloading.cfg文件,記錄下載文件的相關信息及分片信息。

  .downloading文件保存文件的進度,大小,路徑等信息。用於啟動應用時,讀取並渲染續傳列表,顯示文件名,文件大小,進度條等信息。

let json = {
          percent: percent,
          filesize: file.filesize,
          md5: file.md5,
          uid: file.uid ,
          bucketName: file.bucketName,
          path: file.path,
        }
        try {
          !stream.closed && fs.writeFileSync(file.path + '.downloading.cfg', JSON.stringify(json))
        } catch(e) {}

5、最后一個分片下載完成 ,將.downloading文件重命名為原文件名並刪除.downloading.cfg文件。

getList

獲取當前目錄下的文件列表。

  獲取文件列表后,算出重命名后的文件名(如果當前目錄有重名文件,則需要將文件重命名。重命名算法見系列文章(一))。將.downloading文件重命名為算出的文件名並刪除.downloading.cfg文件。

if (contentLength < chunkSize) {
          stream.end()
          endStream(file.path)
          try {
            getList(dirname).then(fileList => {
              let newName = fileRename(fileList, filename, 'filename')
              setTimeout(() => {
                fs.rename(file.path + '.downloading', path.join(dirname, newName), (err) => {
                  if (err) {
                    return console.error(err)
                  }
                })
              }, 500)
            })

            fs.unlink(file.path + '.downloading.cfg', function (er) {
              if (er) {
                 return console.error(er);
               }
            })
          } catch (e) { console.log(e) }
        } else {
          !stream.closed && statDir(true)
        }

  至此,文件分片下載完成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM