当前位置: 代码网 > it编程>前端脚本>Node.js > node文件资源管理器的解压缩从零实现

node文件资源管理器的解压缩从零实现

2024年05月19日 Node.js 我要评论
解压缩这里使用较为常用的 7z 来处理压缩包,它可以解开常见的压缩包格式unpacking only: apfs, ar, arj, cab, chm, cpio, cramfs, dmg, ext,

解压缩

这里使用较为常用的 7z 来处理压缩包,它可以解开常见的压缩包格式

unpacking only: apfs, ar, arj, cab, chm, cpio, cramfs, dmg, ext, fat, gpt, hfs, ihex, iso, lzh, lzma, mbr, msi, nsis, ntfs, qcow2, rar, rpm, squashfs, udf, uefi, vdi, vhd, vhdx, vmdk, xar and z.

开发

预下载 mac 与 linux 版本的 7z 二进制文件,放置于 explorer-manage/src/7zip/linux 与 /mac 目录内。可前往 7z 官方进行下载,下载链接

也可以使用 7zip-bin 这个依赖,内部包含所有环境可运行的二进制文件。由于项目是由镜像进行运行,使用全环境的包会加大镜像的体积。

所以这里单独下载特定环境的下二进制文件,可能版本会比较旧,最近更新为 2022/5/16 。目前最新的 2023/06/20@23.01 版本。

使用 node-7z 这个依赖处理 7z 的输入输出

安装依赖

pnpm i node-7z

运行文件

// https://laysent.com/til/2019-12-02_7zip-bin-in-alpine-docker
// https://www.npmjs.com/package/node-7z
// https://www.7-zip.org/download.html
// import sevenbin from '7zip-bin'
import node7z from 'node-7z'
import { parsefilepath } from './parse-path.mjs'
import path from 'path'
import { dirname } from 'node:path'
import { fileurltopath } from 'node:url'
import { formatpath } from '../../lib/format-path.mjs'
const __dirname = dirname(fileurltopath(import.meta.url))
/**
 * @type {import('node-7z').sevenzipoptions}
 */
const base_option = {
  $bin: process.platform === 'darwin' ? path.join(__dirname, './mac/7zz') : path.join(__dirname, './linux/7zzs'),
  recursive: true,
  exclude: ['!__macosx/*', '!.ds_store'],
  latesttimestamp: false,
}
/**
 * @param path {string}
 * @param out_path {string|undefined}
 * @param pwd {string | number | undefined}
 * @returns {import('node-7z').zipstream}
 */
export const node7zaunpackaction = (path, out_path = '', pwd = 'pwd') => {
  const join_path = formatpath(path)
  const { file_dir_path } = parsefilepath(join_path)
  return node7z.extractfull(join_path, formatpath(out_path) || `${file_dir_path}/`, {
    ...base_option,
    password: pwd,
  })
}
/**
 * @param path {string}
 * @param pwd {string | number | undefined}
 * @returns {import('node-7z').zipstream}
 */
export const node7zlistaction = (path, pwd = 'pwd') => {
  const join_path = formatpath(path)
  return node7z.list(join_path, { ...base_option, password: pwd })
}

简单封装下 node7zaunpackaction 与 node7zlistaction 方法

  • node7zaunpackaction:解压缩方法
  • node7zlistaction:查看当前压缩包内容

explorer 客户端展示

大致设计为弹窗模式,提供一个解压缩位置,默认当前压缩包位置。再提供一个密码输入栏,用于带密码的压缩包解压。

解压缩一个超大包时,可能会超过 http 的请求超时时间,浏览器会主动关闭这次请求。导致压缩包没有解压缩完毕,请求就已经关闭了。虽然 node 还在后台进行解压缩。但是客户端无法知道是否解压缩完毕。

可通过延长 http 的请求超时时间。也可使用 stream 逐步输出内容的方式避免超时,客户端部分可以实时看到当前解压缩的进度。类似像 ai 机器人提问时,文字逐字出现的效果。

查看压缩包内容

直接使用 server action 调用 node7zlistaction 方法即可

解压缩

使用 node-7z 的输出流逐步输出到浏览器

封装一个 post api 接口。

  • 监听 node-7z 返回的数据流 .on('data') 事件。
  • 对数据流做 encoder.encode(json.stringify(value) + ‘, ’) 格式化操作。方便客户端读取数据流。
  • 每秒往客户端输出一个时间戳避免请求超时 stream.push({ loading: date.now() })
  • 10 分钟后关闭 2 的定时输出,让其自然超时。
  • 客户端通过 fetch 获取数据流,具体可以看 unpack 方法

接口 api

import { nextrequest, nextresponse } from 'next/server'
import { node7zaunpackaction } from '@/explorer-manager/src/7zip/7zip.mjs'
import { nodestreamtoiterator } from '@/explorer-manager/src/main.mjs'
const encoder = new textencoder()
const iteratortostream = (iterator: asyncgenerator) => {
  return new readablestream({
    async pull(controller) {
      const { value, done } = await iterator.next()
      if (done) {
        controller.close()
      } else {
        controller.enqueue(encoder.encode(json.stringify(value) + ', '))
      }
    },
  })
}
export const post = async (req: nextrequest) => {
  const { path, out_path, pwd } = await req.json()
  try {
    const stream = node7zaunpackaction(path, out_path, pwd)
    stream.on('data', (item) => {
      console.log('data', item.file)
    })
    const interval = setinterval(() => {
      console.log('interval', stream.info)
      stream.push({ loading: date.now() })
    }, 1000)
    const timeout = settimeout(
      () => {
        clearinterval(interval)
      },
      60 * 10 * 1000,
    )
    stream.on('end', () => {
      console.log('end', stream.info)
      stream.push({
        done: json.stringify(object.fromentries(stream.info), null, 2),
      })
      cleartimeout(timeout)
      clearinterval(interval)
      stream.push(null)
    })
    return new nextresponse(iteratortostream(nodestreamtoiterator(stream)), {
      headers: {
        'content-type': 'application/octet-stream',
      },
    })
  } catch (e) {
    return nextresponse.json({ ret: -1, err_msg: e })
  }
}

客户端弹窗组件

'use client'
import react, { usestate } from 'react'
import { card, modal, space, table } from 'antd'
import unpackform from '@/components/unpack-modal/unpack-form'
import { isempty } from 'lodash'
import { userequest } from 'ahooks'
import bit from '@/components/bit'
import dateformat from '@/components/date-format'
import { unpackitemtype } from '@/explorer-manager/src/7zip/types'
import { useunpackpathdispatch, useunpackpathstore } from '@/components/unpack-modal/unpack-path-context'
import { useupdatereaddirlist } from '@/app/path/readdir-context'
import { unpacklistaction } from '@/components/unpack-modal/action'
let pack_list_path = ''
const unpackmodal: react.fc = () => {
  const unpack_path = useunpackpathstore()
  const changeunpackpath = useunpackpathdispatch()
  const [unpack_list, changeunpacklist] = usestate<unpackitemtype['list']>([])
  const { update } = useupdatereaddirlist()
  const packlist = userequest(
    async (form_val) => {
      pack_list_path = unpack_path
      const { pwd } = await form_val
      return unpacklistaction(unpack_path, pwd)
    },
    {
      manual: true,
    },
  )
  const unpack = userequest(
    async (form_val) => {
      pack_list_path = unpack_path
      unpack_list.length = 0
      const { out_path, pwd } = await form_val
      const res = await fetch('/path/api/unpack', {
        method: 'post',
        body: json.stringify({ path: unpack_path, out_path, pwd: pwd }),
      })
      if (res.body) {
        const reader = res.body.getreader()
        const decode = new textdecoder()
        while (1) {
          const { done, value } = await reader.read()
          const decode_value = decode
            .decode(value)
            .split(', ')
            .filter((text) => boolean(string(text).trim()))
            .map((value) => {
              try {
                return value ? json.parse(value) : { value }
              } catch (e) {
                return { value }
              }
            })
            .filter((item) => !item.loading)
            .reverse()
          !isempty(decode_value) && changeunpacklist((unpack_list) => decode_value.concat(unpack_list))
          if (done) {
            break
          }
        }
      }
      return promise.resolve().then(update)
    },
    {
      manual: true,
    },
  )
  return (
    <modal
      title="解压缩"
      open={!isempty(unpack_path)}
      width={1000}
      oncancel={() => changeunpackpath('')}
      footer={false}
      destroyonclose={true}
    >
      <unpackform packlist={packlist} unpack={unpack} />
      <space direction="vertical" style={{ width: '100%' }}>
        {pack_list_path === unpack_path && !isempty(unpack_list) && (
          <card
            title="unpack"
            bodystyle={{
              maxheight: '300px',
              overflowy: 'scroll',
              paddingtop: 20,
              overscrollbehavior: 'contain',
            }}
          >
            {unpack_list.map(({ file, done }) => (
              <pre key={file || done}>{file || done}</pre>
            ))}
          </card>
        )}
        {pack_list_path === unpack_path && !isempty(packlist.data) && (
          <card title="压缩包内容">
            {!isempty(packlist.data?.data) && (
              <table
                scroll={{ x: true }}
                rowkey={({ file }) => file}
                columns={[
                  { key: 'file', dataindex: 'file', title: 'file' },
                  {
                    key: 'size',
                    dataindex: 'size',
                    title: 'size',
                    width: 100,
                    render: (size) => {
                      return <bit>{size}</bit>
                    },
                  },
                  {
                    key: 'sizecompressed',
                    dataindex: 'sizecompressed',
                    title: 'sizecompressed',
                    width: 150,
                    render: (size) => {
                      return <bit>{size}</bit>
                    },
                  },
                  {
                    key: 'datetime',
                    dataindex: 'datetime',
                    title: 'datetime',
                    width: 180,
                    render: (date) => <dateformat>{new date(date).gettime()}</dateformat>,
                  },
                ]}
                datasource={packlist.data?.data}
              />
            )}
            {packlist.data?.message && <p>{packlist.data?.message}</p>}
          </card>
        )}
      </space>
    </modal>
  )
}
export default unpackmodal

测试用逐字输出

每秒往客户端输出当前时间。持续 10 分钟。

import { iteratortostream, nodestreamtoiterator } from '@/explorer-manager/src/main.mjs'

function sleep(time: number) {
  return new promise((resolve) =&gt; {
    settimeout(resolve, time)
  })
}

const encoder = new textencoder()

async function* makeiterator() {
  let length = 0
  while (length &gt; 60 * 10) {
    await sleep(1000)
    yield encoder.encode(`&lt;p&gt;${length} ${new date().tolocalestring()}&lt;/p&gt;`)

    length += 1
  }
}

export async function post() {
  return new response(iteratortostream(nodestreamtoiterator(makeiterator())), {
    headers: { 'content-type': 'application/octet-stream' },
  })
}

export async function get() {
  return new response(iteratortostream(nodestreamtoiterator(makeiterator())), {
    headers: { 'content-type': 'html' },
  })
}

效果

git-repo

yangws29/share-explorer

以上就是node文件资源管理器的解压缩从零实现的详细内容,更多关于node文件资源解压缩的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com