软件项目技术点(21)——自动保存和恢复
AxeSlide软件项目梳理 canvas绘图系列知识点整理
自动保存的基本思路
1)软件每次打开都会创建一个保存画布元素信息的文件,文件名是在打开软件时生成的唯一字符串。可同时打开多个窗口,所以保存文件路径下work/context文件夹里可能会有多个文件。
2)每隔2分钟就更新一次context.json文件
3)软件窗口正常关闭时将该窗口对应的context.json文件自动删除,如果仅打开了这一个窗口,将work下其他资源文件(插入的图片等)也删除掉。
软件窗口如果是非正常关闭(电脑断电,电脑死机,软件崩溃等),context.json文件和work下的资源文件都不会被删除
4)当软件再次被打开时,如果无其他窗口打开并且work/context目录下面有context.json文件存在,这时候软件会提醒有文件需要恢复保存。
保存context.json文件
针对过程2)每间隔2分钟context.json文件被保存一次,借助游戏主循环《软件项目技术点(1)——游戏主循环机制》,我们在循环主函数run()里添加如下代码,判断是否需要进行一次保存。
1 //自动保存context 2 if (!that.isGoto && editor.canvas.extendIntervalId < 0) { 3 var autoSaveSetting = index.autoSaveContext; 4 if (that.commonElements.count() >= 1 && curTime - autoSaveSetting.lastSaveTime > 60 * 1000 * <number>autoSaveSetting.saveTime) { 5 autoSaveSetting.saveContext(); 6 } 7 }
如何将画布对象信息写入一个.json文件?
我们先给大家认识一下我们代码里利用到的这三个fs函数
(1)打开文件
fs.open(filename, flags, [mode], callback);
1 // fs.open(filename, flags, [mode], callback); 2 3 /** 4 * filename, 必选参数,文件名 5 * flags, 操作标识,如"r",读方式打开,'r' 写方式打开 6 * [mode],权限,如777,表示任何用户读写可执行 7 * callback 打开文件后回调函数,参数默认第一个err, 8 第二个fd为一个整数,表示打开文件返回的文件描述符,window中又称文件句柄 9 */ 10 11 fs.open(__dirname + '/test.txt', 'r', '0666', function (err, fd) { 12 console.log(fd); 13 });
(2)写文件,将缓冲区内数据写入使用fs.open打开的文件
fs.write(fd, buffer, offset, length, position, callback);
1 //fs.write(fd, buffer, offset, length, position, callback); 2 3 /** 4 * fd, 使用fs.open打开成功后返回的文件描述符 5 * buffer, 一个Buffer对象,v8引擎分配的一段内存 6 * offset, 整数,从缓存区中读取时的初始位置,以字节为单位 7 * length, 整数,从缓存区中读取数据的字节数 8 * position, 整数,写入文件初始位置; 9 * callback(err, written, buffer), 写入操作执行完成后回调函数,written实际写入字节数,buffer被读取的缓存区对象 10 */ 11 12 fs.open(__dirname + '/test.txt', 'a', function (err, fd) { 13 if(err) { 14 console.error(err); 15 return; 16 } else { 17 var buffer = new Buffer('写入文件数据内容'); 18 //写入'入文件'三个字 19 fs.write(fd, buffer, 3, 9, 12, function (err, written, buffer) { 20 if(err) { 21 console.log('写入文件失败'); 22 console.error(err); 23 return; 24 } else { 25 console.log(buffer.toString()); 26 //写入'数据内'三个字 27 fs.write(fd, buffer, 12, 9, null, function (err, written, buffer) { 28 console.log(buffer.toString()); 29 }) 30 } 31 }); 32 } 33 });
(3)刷新缓存区;
fs.fsync(fd, [callback])
1 // 使用fs.write写入文件时,操作系统是将数据读到内存,再把数据写入到文件中,当数据读完时并不代表数据已经写完,因为有一部分还可能在内在缓冲区内。 2 // 因此可以使用fs.fsync方法将内存中数据写入文件;--刷新内存缓冲区; 3 4 //fs.fsync(fd, [callback]) 5 /** 6 * fd, 使用fs.open打开成功后返回的文件描述符 7 * [callback(err, written, buffer)], 写入操作执行完成后回调函数,written实际写入字节数,buffer被读取的缓存区对象 8 */ 9 10 fs.open(__dirname + '/test.txt', 'a', function (err, fd) { 11 if(err) 12 throw err; 13 var buffer = new Buffer('我爱nodejs编程'); 14 fs.write(fd, buffer, 0, 9, 0, function (err, written, buffer) { 15 console.log(written.toString()); 16 fs.write(fd, buffer, 9, buffer.length - 9, null, function (err, written) { 17 console.log(written.toString()); 18 fs.fsync(fd); 19 fs.close(fd); 20 }) 21 }); 22 });
注意:每个fd都要调用fs.close(fd)关闭流对象,否则会出错。
我们使用上面介绍的三个fs的api函数完成将画布对象信息写入context.json文件的代码如下:
1 //保存context文件 2 saveContext() { 3 var that = this; 4 try { 5 var dtoCore = editor.getDtoCore(true, false); 6 var dtoCoreObj = JSON.decycle(dtoCore, true); 7 var context = JSON.stringify(dtoCoreObj); 8 Common.FileSytem.fsExt.open(Common.FileSytem.contextDir + this.saveContextPath + ".tmp", "w", function (err, fd) { 9 if (err) { 10 Common.Logger.setErrLog(Common.LogCode.saveContext, "文件:AutoSaveContext,saveContext方法出错:" + err.message); 11 Common.FileSytem.fsExt.closeSync(fd); 12 } else { 13 Common.FileSytem.fsExt.write(fd, context, function (err) { 14 if (err) { 15 Common.Logger.setErrLog(Common.LogCode.saveContext, "文件:AutoSaveContext,saveContext方法出错:" + err.message); 16 Common.FileSytem.fsExt.closeSync(fd); 17 } else { 18 Common.FileSytem.fsExt.fsync(fd, function (err) { 19 if (err) { 20 Common.Logger.setErrLog(Common.LogCode.saveContext, "文件:AutoSaveContext,saveContext方法出错:" + err.message); 21 Common.FileSytem.fsExt.closeSync(fd); 22 } else { 23 Common.FileSytem.fsExt.closeSync(fd); 24 Common.FileSytem.fsExt.rename(Common.FileSytem.contextDir + that.saveContextPath + ".tmp", Common.FileSytem.contextDir + that.saveContextPath, function (err) { 25 if (err) { 26 Common.Logger.setErrLog(Common.LogCode.saveContext, "文件:AutoSaveContext,saveContext方法出错:" + err.message); 27 } 28 }); 29 } 30 }) 31 } 32 }) 33 } 34 }); 35 } catch (err) { 36 Common.Logger.setErrLog(Common.LogCode.saveContext, "文件:AutoSaveContext,saveContext方法出错:" + err.message); 37 } 38 39 this.lastSaveTime = Date.now();//执行本次保存的具体时间点 40 }
注意:跟保存作品一样,为避免一个已存在context文件再次保存时失败将已存在的正确文件覆盖掉,我们新建文件context.json.tmp,文件写入成功后再重命名为context.json文件。
检查是否有需要修复文件
在关闭软件窗口时需要判断是否有需要修复文件,以此得出是否要删除资源
在软件打开时也需要判断是否有需要修复文件,以此控制是否需要弹出恢复文件窗口
1 //检查是否有需要修复的文件 2 isNeedToRestore(callback: Function = null) { 3 var that = this; 4 var fileList = Common.FileSytem.readDirSync(Common.FileSytem.contextDir);//如果context文件夹下没有文件时,说明没有需要修复的文件再进行清除 5 fileList.forEach(function (name, i) { 6 if (name.indexOf(".tmp") == -1) {//临时文件.tmp不包含在内 7 that.needRestoreNames.push(name); 8 } 9 }) 10 if (that.needRestoreNames.length >= 1) { 11 var allWindows = chrome.app.window.getAll();//获取打开软件窗口的个数 12 if (that.needRestoreNames.length >= 1 && allWindows.length == 1) {//如果context.json有多个,且只打开一个软件窗口情况 13 return true; 14 } else { 15 return false; 16 } 17 18 } else { 19 return false; 20 } 21 }
恢复文件
跟保存文件的过程是一样的《软件项目技术点(19)——文件的保存和打开(解压缩)》
fs用法参考资料: