zoukankan      html  css  js  c++  java
  • 用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)
            }

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

  • 相关阅读:
    SCOI2007 组队
    ZJOI2008 生日聚会
    织梦标签截取指定数目字符
    HTML特殊符号对照表
    DedeCMS让{dede:list}标签支持weight权重排序
    PHP 5.4 中经 htmlspecialchars 转义后的中文字符串为空,DeDeCMS在PHP5.4下编辑器中文不显示问题
    把图文内容载入微应用中
    织梦安装错误
    dedecms创建或修改目录失败
    安装phpmyadmin
  • 原文地址:https://www.cnblogs.com/shawnyung/p/10097959.html
Copyright © 2011-2022 走看看