/** * 模拟客户端back server * 目前还不知道back的API及具体实现,暂时先抑制代码报错 */ import { fetchApi, wrapErrorHint } from '@/utils/request'; import { notify, firstCharToUpperCase } from '@/utils/tool'; import { identity } from 'lodash'; import Vue from 'vue'; export const isClient = !!global.electron; // process.env.IS_CLIENT; let socket; const noop = () => {}; const io = (path) => { const sk = new WebSocket(`ws://127.0.0.1:7777/${path}`); sk.on = sk.addEventListener; return sk; } const requestBySocket = (socketIns, message) => new Promise((resolve, reject) => { socketIns.on('open', () => { socketIns.send(message); }); socketIns.on('message', e => {resolve(e);socketIns.close();}); socketIns.on('error', e => reject(e)); }); const safeCall = f => isClient ? f : noop; const safeSocket = f => (...args) => socket && f(...args); const system = { isClient, init: safeCall(() => { console.log('客户端 electron API 检测:', global.electron); }), // initialSocket: () => { // socket = io('ws://10.240.5.17:8000'); // // socket = io('ws://127.0.0.1:3000'); // debugger; // socket.on('connect', () => { console.log('本地socket服务连接成功') }); // }, /** * todo * 通知登录 */ login: () => { }, /** * todo * 通知登出 */ logout: () => { }, /** * 通知系统进入当前的工作空间 */ entryProject: safeCall((projName, userId, fileChangeHandler, initHandler, errorHandler = identity) => { requestBySocket(io('init'), [userId, projName].join('|')) .then(response => initHandler(response.data)); const watchSocket = io('subscriptionFileChange'); watchSocket.on('open', () => { watchSocket.send(projName); }); watchSocket.on('message', fileChangeHandler); watchSocket.on('error', errorHandler); return watchSocket; }), /** * todo * 离开工作空间时注销socket */ leaveProject: safeCall((watchSocket) => { watchSocket.close(); }), /** * 下载文件到工作空间 */ downloadFile: safeCall((fileIpfsId, projectName, fileName, dirName, onProcessHandler, onErrorHandler = identity) => { const socket = io('download'); socket.on('open', () => { socket.send([fileIpfsId, projectName, fileName, dirName].join('|')); }); socket.on('message', (e) => { if(e.data === '-1') { notify.error('文件下载失败'); onErrorHandler(e); return; } if(e.data === '-2') { notify.error('请先关闭本地文件后再下载'); onErrorHandler(e); return; } onProcessHandler(e, socket); }); socket.on('error', e => { onErrorHandler(e); }); }), // 解析文件/文件夹路径信息 // { // name: '11-28会议纪要.docx', // extension:".docx", // relativePath:"协同项目\\分析\\11-28会议纪要.docx", // absolutePath:"C:\\Users\\yuan_rh\\easycloud\\332174685636661248\\协同项目\\分析\\11-28会议纪要.docx" // } analyzeSystemPath: safeCall(async (systemFullpath) => { const response = await requestBySocket(io('getFolderFileInfo'), systemFullpath); try { const obj = JSON.parse(response.data); const uploadTasks = Object.values(obj).map((data) => { const { name: extensionedFileName, extension: dotExtension, relativePath, absolutePath } = data; const extension = dotExtension.indexOf('.') === 0 ? dotExtension.slice(1) : dotExtension; const fileName = extensionedFileName.slice(0, -dotExtension.length); return { fileName, extension, relativePath: relativePath.replace(/(\\)+/g, '/'), fullPath: absolutePath, }; }); return uploadTasks; } catch(e) { return []; } }), // 选择文件 chooseFiles: safeCall(async () => { const { ipcRenderer } = global.electron; const res = await ipcRenderer.invoke('project-choose-files'); const { canceled, filePaths } = res; if(canceled) return null; return filePaths; }), // 选择文件 chooseFolders: safeCall(async () => { const { ipcRenderer } = global.electron; const res = await ipcRenderer.invoke('project-choose-folders'); const { canceled, filePaths } = res; if(canceled) return null; return filePaths; }), /** * 上传文件到工作空间 * 程序步骤: * + 用户选择本地文件/文件夹 * + 文件上传至本地ipfs节点 * + 将文件的ipfsCid连同文件信息发送到远端服务器 * @param {params.projectId} 项目Id * @param {params.projectName} 项目名称 * @param {params.folderId} 节点文件夹Id * @param {params.folderName} 节点文件夹Id * @param {params.folderLevelId} 节点文件夹levelId * @param {params.distFileRelativePath} 节点文件夹下的相对路径, 直接在节点文件夹下则为空 * @param {params.sourceFilePath} 上传文件的完整路径 * @param {params.fileName} 上传文件名 * @param {params.fileExtension} 上传文件名 * @param {params.fileList} 用于对比文件名是否重复的文件队列 * @param {params.onSuccessHandler} 完成上传时的回调 * @param {params.onProgressHandler} 上传进度反馈的回调 * @param {params.onErrorHandler} 上传失败的回调 * */ uploadFile: safeCall(async (params) => { const { projectId, projectName, folderId, folderName, folderLevelId, distFileRelativePath = '', fileName, fileExtension, sourceFilePath, fileList, onSuccess: onSuccessHandler, onProgress: onProgressHandler = identity, onError: onErrorHandler = identity } = params; const extensionedFileName = fileExtension ? `${fileName}.${fileExtension}`: fileName; const distFilePath = `${folderName}/${distFileRelativePath}`; const maybeFile = fileList.find(iFile => distFileRelativePath === iFile.relativePath && `${iFile.archName}${iFile.extension ? `.${iFile.extension}` : ''}` === extensionedFileName); // 检测当前工作目录中是否存在同名文件 if(maybeFile) { let confirmRes = false; try { await Vue.prototype.$confirm('监测到文件夹存在同名文件,是否继续上传并覆盖同名文件?'); confirmRes = true; } catch(e) { console.log('user canceled'); } if(!confirmRes) return; } const uploadFile = maybeFile ? firstCharToUpperCase({ ...maybeFile, ModifyUserId: sessionStorage.userId }) : { // 文件名称 不带扩展名 ArchName: fileName, // CommonStatus: 0, // CreateTime: "string", // 文件上传者Id CreateUserId: sessionStorage.userId, // Deleted: 0, Extension: fileExtension, // 文件大小 单位? // FileSize: +size, // 所处文件夹id FolderId: folderId, // 所处文件夹层级,拼接符:_ FolderLevelId: folderLevelId, Id: `upload:${`${Math.random()}`.slice(2, 8)}`, // IpfsCid: hash, // IsShowRecycle: 0, // Milestone: 0, // ModifyTime: "string", // ModifyUserId: 0, // 项目id ProjId: projectId, RelativePath: distFileRelativePath, // ShowUrl: "string", Status: 2, Version: 1, // WorkStatus: 0 }; const socket = io('upload'); socket.on('open', () => { const data = [sourceFilePath, extensionedFileName, projectName, distFilePath].join('|'); socket.send(data); onProgressHandler({ process: 0 }, uploadFile); }); socket.on('message', async (e) => { console.log('receive download file message:', e); if(e.data === '-1') { notify.error('文件上传失败'); onErrorHandler(e, uploadFile); return; } if(e.data === '-2') { notify.error('请先关闭本地文件后再上传'); onErrorHandler(e, uploadFile); return; } try { const progressData = JSON.parse(e.data); const { size, process, hash } = progressData; onProgressHandler(progressData, uploadFile); if(process !== 100 || !hash) return; socket.close(); // {"size":"88.69","currentSize":"88.69","unit":"KiB","process":100,"hash":""} // {"size":"","currentSize":"","unit":"","process":100,"hash":"QmPJ9i4z5UdoQpLH1DrkhZiTZra2rGicXiPabiLw4LvTmX"} // const maybeFile = fileList.find(iFile => `${iFile.archName}${iFile.extension ? `.${iFile.extension}` : ''}` === extensionedFileName); uploadFile.FileSize = +size; uploadFile.IpfsCid = hash; if(!maybeFile) { delete uploadFile.Id; } const res = await fetchApi(`file/${maybeFile ? 'updateFile' : 'addFile'}`, uploadFile); wrapErrorHint(res); if(res.Code !== 0) return; //notify.success(maybeFile ? '上传成功, 已覆盖同名文件' : '上传成功'); notify.success('文件已上传。') onSuccessHandler(uploadFile); } catch (e) { console.error('socket-upload-file parse data have error:', e); onErrorHandler(e, uploadFile); } }); socket.on('error', e => { onErrorHandler(e, uploadFile); }); }), /** * 上传文件到工作空间 * 程序步骤: * + 用户选择本地文件/文件夹 * + 文件上传至本地ipfs节点 * + 将文件的ipfsCid连同文件信息发送到远端服务器 */ uploadFileOld: safeCall(async (projectId, projectName, folderId, folderName, levelId, fileList, onSuccessHandler, onProgressHandler = identity, onErrorHandler = identity) => { const { ipcRenderer } = global.electron; const res = await ipcRenderer.invoke('project-upload-file'); console.log('ipcRenderer project-selected-upload-file: ', res); const { canceled, filePaths } = res; if(canceled) return; const filePath = filePaths[0]; const extensionedFileName = filePath.split(/\/|\\/g).pop(); const tempFilePaths = extensionedFileName.split('.'); const extension = tempFilePaths.length > 1 ? tempFilePaths.pop() : ''; const fileName = tempFilePaths.join('.'); const maybeFile = fileList.find(iFile => `${iFile.archName}${iFile.extension ? `.${iFile.extension}` : ''}` === extensionedFileName); // 检测当前工作目录中是否存在同名文件 if(maybeFile) { let confirmRes = false; try { await Vue.prototype.$confirm('监测到文件夹存在同名文件,是否继续上传并覆盖同名文件?'); confirmRes = true; } catch(e) { console.log('user canceled'); } if(!confirmRes) return; } const uploadFile = maybeFile ? firstCharToUpperCase({ ...maybeFile, ModifyUserId: sessionStorage.userId }) : { // 文件名称 不带扩展名 ArchName: fileName, // CommonStatus: 0, // CreateTime: "string", // 文件上传者Id CreateUserId: sessionStorage.userId, // Deleted: 0, Extension: extension, // 文件大小 单位? // FileSize: +size, // 所处文件夹id FolderId: folderId, // 所处文件夹层级,拼接符:_ FolderLevelId: levelId, Id: `upload:${`${Math.random()}`.slice(2, 8)}`, // IpfsCid: hash, // IsShowRecycle: 0, // Milestone: 0, // ModifyTime: "string", // ModifyUserId: 0, // 项目id ProjId: projectId, // ShowUrl: "string", Status: 2, Version: 1, // WorkStatus: 0 }; const socket = io('upload'); socket.on('open', () => { const data = [filePath, extensionedFileName, projectName, folderName].join('|'); socket.send(data); onProgressHandler({ process: 0 }, uploadFile); }); socket.on('message', async (e) => { console.log('receive download file message:', e); if(e.data === '-1') { notify.error('文件上传失败'); onErrorHandler(e, uploadFile); return; } if(e.data === '-2') { notify.error('请先关闭本地文件后再上传'); onErrorHandler(e, uploadFile); return; } try { const progressData = JSON.parse(e.data); const { size, process, hash } = progressData; onProgressHandler(progressData, uploadFile); if(process !== 100 || !hash) return; socket.close(); // {"size":"88.69","currentSize":"88.69","unit":"KiB","process":100,"hash":""} // {"size":"","currentSize":"","unit":"","process":100,"hash":"QmPJ9i4z5UdoQpLH1DrkhZiTZra2rGicXiPabiLw4LvTmX"} // const maybeFile = fileList.find(iFile => `${iFile.archName}${iFile.extension ? `.${iFile.extension}` : ''}` === extensionedFileName); uploadFile.FileSize = +size; uploadFile.IpfsCid = hash; if(!maybeFile) { delete uploadFile.Id; } const res = await fetchApi(`file/${maybeFile ? 'updateFile' : 'addFile'}`, uploadFile); wrapErrorHint(res); if(res.Code !== 0) return; //notify.success(maybeFile ? '上传成功, 已覆盖同名文件' : '上传成功'); notify.success('文件已上传。') onSuccessHandler(uploadFile); } catch (e) { console.error('socket-upload-file parse data have error:', e); onErrorHandler(e, uploadFile); } }); socket.on('error', e => { onErrorHandler(e, uploadFile); }); }), /** * 更新本地文件 */ updateFile: safeCall((file, localFilePathPrefix, projectName, folderName, onSuccessHandler, onProgressHandler = identity, onErrorHandler = identity) => { const socket = io('upload'); const { archName, extension, id: fileId } = file; const extensionedFileName = `${archName}${extension ? `.${extension}` : ''}`; const filePath = `${localFilePathPrefix}\\${folderName}\\${extensionedFileName}`; socket.on('open', () => { const data = [filePath, extensionedFileName, projectName, folderName].join('|'); socket.send(data); }); socket.on('message', async (e) => { if(e.data === '-1') { notify.error('文件上传失败'); onErrorHandler(e); return; } if(e.data === '-2') { notify.error('请先关闭本地文件后再上传'); onErrorHandler(e); return; } try { const progressData = JSON.parse(e.data); const { size, process, hash } = progressData; onProgressHandler(progressData); if(process !== 100 || !hash) return; socket.close(); const copyFile = firstCharToUpperCase({ ...file, ipfsCid: hash, size: +size, ModifyUserId: sessionStorage.userId }); const res = await fetchApi('file/updateFile', copyFile); wrapErrorHint(res); // if(res.Code === 0) { notify.success(`${archName} 更新成功`); } onSuccessHandler(copyFile); return; } catch(err) { console.error('socket-update-file parse data have error:', e); // todo 上传失败 } }); socket.on('error', e => { onErrorHandler(e, file); }); }), /** * 系统打开文件 */ openFile: safeCall((filePath) => { const { shell } = global.electron; shell.openPath(filePath); }), /** * 系统打开文件目录 */ openFolder: safeCall((filePath) => { const { shell } = global.electron; shell.showItemInFolder(filePath); }), } export default system;