欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

用Electron开发企业网盘(二)--分片下载

程序员文章站 2022-06-24 23:02:12
书接上文,背景见:https://www.cnblogs.com/shawnyung/p/10060119.html HTTP请求头 Range 请求资源的部分内容(不包括响应头的大小),单位是byte,即字节,从0开始。 请求资源的部分内容(不包括响应头的大小),单位是byte,即字节,从0开始。 ......

  书接上文,背景见: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)
        }

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