保存文件
保存內容有哪些?
我們需要保存的內容信息
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++){//拼接壓縮文件內容字符串all 26 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.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 }
文章列表