• 软件项目技术点(19)——文件的保存和打开(解压缩)


    AxeSlide软件项目梳理   canvas绘图系列知识点整理

    保存文件

    保存内容有哪些?

    我们需要保存的内容信息

    1)context.json 存储画布状态信息和所有元素的配置信息(这个文件在过程中生成)

    2)插入的图片、音频、视频等资源

    3)所用到的字体文件

    4)每一帧的缩略图

    将这些文件压缩到一个zip包里,我们的作品保存后的文件是dbk后缀格式的,其实就是一个压缩zip文件。

    保存过程步骤解析

    1)获取要保存的对象信息dtoCore,后面将其转换成字符串string后存储到文件里。

     2)将保存作品用到的资源整理到fileList中,fileList对象列表有rawPath(原路径)和target(目标路径)。

     3)利用模块zip-addon 将所有的file利用zip.exe压缩到目标路径

    require("zip-addon") 可以从github上下载:https://github.com/liufangfang/zip-addon

    下面是该模块的主要代码:

     1 var fs = require('fs');
     2 var cp = require('child_process');
     3 var path = require('path');
     4 var platform = process.platform;
     5 
     6 function zipFiles(listfile){//利用zip.exe根据list.txt文件里的内容来处理压缩文件
     7     var exeName = platform == 'win32' ? 'win/zip.exe' : 'mac/zip.out';
     8     try{
     9         cp.execFileSync(
    10             path.join(__dirname, exeName),
    11             [listfile], 
    12             {cwd: process.cwd()}
    13         );
    14         return '';
    15     }
    16     catch(e){
    17         return String(e.stdout);
    18     }
    19 }
    20 
    21 function createDbkFile2(fileList, targetZipPath){
    22     var delimiter = platform == 'win32' ? "|" : ":";
    23     var all = targetZipPath + '
    ';
    24     var len = fileList.length;
    25     for(var i=0; i<len; i++){//拼接压缩文件内容字符串all
    26         var rawPath = String(fileList[i].rawPath);
    27         var targetPath = String(fileList[i].targetPath);
    28         all += rawPath + delimiter + targetPath + '
    ';
    29     }
    30     var listFile;
    31     if (platform == 'win32') {
    32         listFile = path.join(__dirname, 'win/list.txt');
    33     }
    34     else {
    35         listFile = path.join(__dirname, 'mac/list.txt');
    36     }
    37     try {
    38         fs.writeFileSync(listFile, all, 'utf8');//将字符串写入list.txt
    39     }
    40     catch(e) {
    41         return e.message;
    42     }
    43     return zipFiles(listFile);
    44 }
    45 
    46 exports.createDbkFile2 = createDbkFile2;

    保存中注意的问题

    1)过滤掉重复文件

    2)保存失败怎么办,我们这里处理可重试三次

    3)一个已存在文件再次保存时失败不应该将已存在的正确文件覆盖掉

    针对第一个问题,过滤重复文件的代码如下:

     1 static distinctList(inList) {
     2     var outList = [], distinct = {};
     3     var i, len = inList.length;
     4     for (i = 0; i < len; i++) {
     5         var obj = inList[i];
     6         if (!distinct[obj.targetPath]) {
     7             outList.push(obj);
     8             distinct[obj.targetPath] = true;
     9         }
    10     }
    11     return outList;
    12 }

    针对上述第三个问题,我们先保存到path+“.temp”文件,成功后再将源文件删除,将path+“.temp”文件重命名为要保存的文件名

     1 public createDbkFile(path, dtoCore, onSuccess: Function) {
     2     var version = "";
     3     var that = this;
     4     this.getDtoFileList(dtoCore, true, true, function (bool, fileList) {//fileList要压缩的文件列表
     5         if (!bool) {
     6             onSuccess(bool);
     7             return;
     8         }
     9         //将dtoCore对象的clipImage置空,减少context的大小
    10         dtoCore.frames.frames.foreach(function (i, obj) {
    11             obj.clipImage = null;
    12         })
    13         //净化dtoCore
    14         dtoCore.fontsUsed = null;
    15         dtoCore.textsUsed = null;
    16         var dtoCoreObj = JSON.decycle(dtoCore, true);
    17         var packageConfig = JSON.stringify(dtoCoreObj);
    18         var dbkTempPath = path + ".temp";//保存文件的临时文件名
    19         Common.FileSytem.createSmallDbk(packageConfig, fileList, dbkTempPath, (e) => {//temp临时文件成功后的回调函数
    20             if (e) {
    21                 Common.Logger.setErrLog(Common.LogCode.savedbk, "文件:Editor,方法:createDbkFile,异常信息:" + e);
    22                 onSuccess(false);
    23             }
    24             else {
    25                 try {
    26                     if (Common.FileSytem.existsSync(path)) {//判断是否已经存在该文件
    27                         Common.FileSytem.fsExt.removeSync(path);
    28                     }
    29                     Common.FileSytem.renameSync(dbkTempPath, path.replace(/\/g, "/").split("/").pop());
    30                     if (fileList.get(0) && (fileList.get(0).targetPath == "cover.png") && !editor.isUploadFile) {
    31                         index.template.saveLocal(path.replace(/\/g, "/"), fileList.get(0).rawPath);
    32                     }
    33                     if (Common.FileSytem.checkZipIntegrity(path)) {//检查zip文件完整性
    34                         onSuccess(true);
    35                     } else {
    36                         Common.Logger.setErrLog(Common.LogCode.savedbk, "文件:Editor,方法:createDbkFile(1),异常信息:" + e);
    37                         onSuccess(false);
    38                     }
    39                 }
    40                 catch (e) {
    41                     Common.Logger.setErrLog(Common.LogCode.savedbk, "文件:Editor,方法:createDbkFile,异常信息:" + e);
    42                     onSuccess(false);
    43                 }
    44             }
    45         });
    46     });
    47 }
     1 //检查zip文件夹完整性,判断保存的zip文件是否正确
     2 static checkZipIntegrity(file: string, fileCount: number = undefined): boolean {
     3     var fd;
     4     var buf = new Buffer(22);
     5     var fs = Common.FileSytem.fsExt;
     6     try {
     7         fd = Common.FileSytem.fsExt.openSync(file, "r");
     8         var stat = fs.fstatSync(fd);
     9         var len = stat.size;
    10         if (len < 22)
    11             throw new Error("file size too small");
    12         var n = Common.FileSytem.fsExt.readSync(fd, buf, 0, 22, len-22);
    13         if (n != 22)
    14             throw new Error("read size error");
    15         //0x50 0x4b 0x05 0x06
    16         if (buf[0] != 0x50 || buf[1] != 0x4b || buf[2] != 0x05 || buf[3] != 0x06)
    17             throw new Error("zip Integrity head Error");
    18         var fc = buf[8] | (buf[9] << 8);
    19         if (fileCount && (fc != fileCount))
    20             throw new Error("zip Integrity fileCount Error");
    21         Common.FileSytem.fsExt.closeSync(fd);
    22         return true;
    23     }
    24     catch (e) {
    25         Common.FileSytem.fsExt.closeSync(fd);
    26         return false;
    27     }
    28 }

    打开文件

    跟保存过程可以颠倒着来看整个过程,打开的文件是个dbk文件其实就是zip文件

    1. 解压文件

    我们需要进行解压缩文件 require(unzip)。  https://github.com/EvanOxfeld/node-unzip

     1 //打开dbk
     2 static openDbk(filePath, isSetFilePath, callback) {
     3     //解压
     4     var unzip = require('unzip');
     5     var that = this;
     6     try
     7     {
     8         var extractTempFolder = FileSytem.tempDbkPath + Util.getGenerate();//创建一个解压目录
     9         FileSytem.mkdirpSync(extractTempFolder);
    10 
    11         var readStream = this.fs.createReadStream(filePath);//读取zip文件包
    12         var unzipExtractor = unzip.Extract({ path: extractTempFolder })
    13         unzipExtractor.on('error', function (err) {
    14             callback(false);
    15         });
    16         unzipExtractor.on('close',() => {
    17             that.copyExtratedDbkFile(extractTempFolder + "/dbk",(isSuccess) => {//解压完成后,在进行copy过程,将解压出来的资源copy对应的目录下
    18                 isSuccess && isSetFilePath && index.template.saveLocal(filePath.replace(/\/g, "/"), extractTempFolder + "/dbk/cover.png");
    19                 callback(isSuccess, extractTempFolder + "/dbk");
    20             })
    21         });
    22         readStream.pipe(unzipExtractor);
    23       
    24     }
    25     catch (e) {
    26         callback(false);
    27     }
    28 }

    2. 批量copy过程

    解压完成后,再进行copy过程,将解压出来的资源copy对应的目录下。

    基于我们的作品包含的文件可能会很多,我们通过模块(lazystream)来实现边读编写的实现复制功能。

    https://github.com/jpommerening/node-lazystream 

     1 private static copyExtratedDbkFile(sourcePath, callBack) {
     2     var lazyStream = require('lazystream');
     3     //获取所有文件列表遍历读写到对应目录
     4     this.DirUtil.Files(sourcePath, 'file',(err, result: Array<String>) => {
     5         if (result && result.length > 0) {
     6             var steps = 0;
     7             function step() {
     8                 steps++;
     9                 if (steps >= result.length) {
    10                     callBack(true);
    11                 }
    12             }
    13 
    14             result.forEach((value: string, index: number) => {
    15                 var fileName = value;
    16                 if (fileName.toLowerCase().indexOf(".ttf") > -1 || fileName.toLowerCase().indexOf(".otf") > -1) {
    17                     step();
    18                 }
    19                 else if (fileName.toLowerCase().replace(/\/g, "/").indexOf("/frame/") > -1) {//copy文件为缩略图的话,变更目标地址
    20                     var frameName = FileSytem.path.basename(fileName);
    21                     var readable = new lazyStream.Readable(function () {
    22                         return FileSytem.fs.createReadStream(fileName)
    23                     });
    24                     var writable = new lazyStream.Writable(() => {
    25                         return FileSytem.fs.createWriteStream(editor.canvas.canvasImp.framePath + frameName).on('close', function () {
    26                             step();
    27                         });
    28                     });
    29                 }
    30                 else {
    31                     var dest = fileName.replace(/\/g, "/").replace(sourcePath + "/", 'slideview/');
    32                     var readable = new lazyStream.Readable(function () {
    33                         return FileSytem.fs.createReadStream(fileName)
    34                     });
    35 
    36                     var writable = new lazyStream.Writable(() => {
    37                         return FileSytem.fs.createWriteStream(dest).on('close', function () {
    38                             step();
    39                         });
    40                     });
    41                 }
    42                 if (readable) {//读文件流 写文件流
    43                     readable.pipe(writable);
    44                 }
    45             });
    46         }
    47     }, null);
    48 }

    3. 获取一个目录下的所有文件

    复制的过程,我们是一个一个文件进行读写,在复制之前我们用dirUtil.File获取到了目录下所有文件。

      1     //获取一个目录下的所有子目录,所有文件的方法
      2     
      3     export class Dir {
      4         fs = require('fs');
      5         path = require('path');
      6 
      7         /**
      8          * find all files or subdirs (recursive) and pass to callback fn
      9          *
     10          * @param {string} dir directory in which to recurse files or subdirs
     11          * @param {string} type type of dir entry to recurse ('file', 'dir', or 'all', defaults to 'file')
     12          * @param {function(error, <Array.<string>)} callback fn to call when done
     13          * @example
     14          * dir.files(__dirname, function(err, files) {
     15          *      if (err) throw err;
     16          *      console.log('files:', files);
     17          *  });
     18          */
     19         Files(dir, type, callback, /* used internally */ ignoreType) {
     20             var that = this;
     21             var pending,
     22                 results = {
     23                     files: [],
     24                     dirs: []
     25                 };
     26             var done = function () {
     27                 if (ignoreType || type === 'all') {
     28                     callback(null, results);
     29                 } else {
     30                     callback(null, results[type + 's']);
     31                 }
     32             };
     33 
     34             var getStatHandler = function (statPath) {
     35                 return function (err, stat) {
     36                     if (err) return callback(err);
     37                     if (stat && stat.isDirectory() && stat.mode !== 17115) {
     38                         if (type !== 'file') {
     39                             results.dirs.push(statPath);
     40                         }
     41                         that.Files(statPath, type, function (err, res) {
     42                             if (err) return callback(err);
     43                             if (type === 'all') {
     44                                 results.files = results.files.concat(res.files);
     45                                 results.dirs = results.dirs.concat(res.dirs);
     46                             } else if (type === 'file') {
     47                                 results.files = results.files.concat(res.files);
     48                             } else {
     49                                 results.dirs = results.dirs.concat(res.dirs);
     50                             }
     51                             if (!--pending) done();
     52                         }, true);
     53                     } else {
     54                         if (type !== 'dir') {
     55                             results.files.push(statPath);
     56                         }
     57                         // should be the last statement in statHandler
     58                         if (!--pending) done();
     59                     }
     60                 };
     61             };
     62 
     63             if (typeof type !== 'string') {
     64                 ignoreType = callback;
     65                 callback = type;
     66                 type = 'file';
     67             }
     68 
     69             this.fs.stat(dir, function (err, stat) {
     70                 if (err) return callback(err);
     71                 if (stat && stat.mode === 17115) return done();
     72 
     73                 that.fs.readdir(dir, function (err, list) {
     74                     if (err) return callback(err);
     75                     pending = list.length;
     76                     if (!pending) return done();
     77                     for (var file, i = 0, l = list.length; i < l; i++) {
     78                         file = that.path.join(dir, list[i]);
     79                         that.fs.stat(file, getStatHandler(file));
     80                     }
     81                 });
     82             });
     83         }
     84 
     85 
     86         /**
     87          * find all files and subdirs in  a directory (recursive) and pass them to callback fn
     88          *
     89          * @param {string} dir directory in which to recurse files or subdirs
     90          * @param {boolean} combine whether to combine both subdirs and filepaths into one array (default false)
     91          * @param {function(error, Object.<<Array.<string>, Array.<string>>)} callback fn to call when done
     92          * @example
     93          * dir.paths(__dirname, function (err, paths) {
     94          *     if (err) throw err;
     95          *     console.log('files:', paths.files);
     96          *     console.log('subdirs:', paths.dirs);
     97          * });
     98          * dir.paths(__dirname, true, function (err, paths) {
     99          *      if (err) throw err;
    100          *      console.log('paths:', paths);
    101          * });
    102          */
    103         Paths(dir, combine, callback) {
    104 
    105             var type;
    106 
    107             if (typeof combine === 'function') {
    108                 callback = combine;
    109                 combine = false;
    110             }
    111 
    112             this.Files(dir, 'all', function (err, results) {
    113                 if (err) return callback(err);
    114                 if (combine) {
    115 
    116                     callback(null, results.files.concat(results.dirs));
    117                 } else {
    118                     callback(null, results);
    119                 }
    120             }, null);
    121         }
    122 
    123 
    124         /**
    125          * find all subdirs (recursive) of a directory and pass them to callback fn
    126          *
    127          * @param {string} dir directory in which to find subdirs
    128          * @param {string} type type of dir entry to recurse ('file' or 'dir', defaults to 'file')
    129          * @param {function(error, <Array.<string>)} callback fn to call when done
    130          * @example
    131          * dir.subdirs(__dirname, function (err, paths) {
    132          *      if (err) throw err;
    133          *      console.log('files:', paths.files);
    134          *      console.log('subdirs:', paths.dirs);
    135          * });
    136          */
    137         Subdirs(dir, callback) {
    138             this.Files(dir, 'dir', function (err, subdirs) {
    139                 if (err) return callback(err);
    140                 callback(null, subdirs);
    141             }, null);
    142         }
    143     }
  • 相关阅读:
    MySQL视图——学习笔记及实验
    小学生四则运算自动刷题库优化升级
    软件工程小项目——小学生四则运算自动刷题库
    笔记--运算符、表达式和语句
    笔记--基本数据类型与数组
    笔记--java入门
    原因: java.lang.ClassNotFoundException: Hello
    使用gopm代替go get 解决go包卡慢的问题
    调用微信截图功能c# 截图带扩展名
    如何用golang搜索抓取淘宝商品
  • 原文地址:https://www.cnblogs.com/fangsmile/p/6283553.html
Copyright © 2020-2023  润新知