首页 > 代码库 > 软件项目技术点(19)——文件的保存和打开(解压缩)

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

保存文件

保存内容有哪些?

我们需要保存的内容信息

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 + ‘\n‘;24     var len = fileList.length;25     for(var i=0; i<len; i++){//拼接压缩文件内容字符串all26         var rawPath = String(fileList[i].rawPath);27         var targetPath = String(fileList[i].targetPath);28         all += rawPath + delimiter + targetPath + ‘\n‘;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.txt39     }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         //净化dtoCore14         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 0x0616         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 fn126          *127          * @param {string} dir directory in which to find subdirs128          * @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 done130          * @example131          * 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     }

 

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