Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 
 

524 lignes
18 KiB

  1. /**
  2. * 模拟客户端back server
  3. * 目前还不知道back的API及具体实现,暂时先抑制代码报错
  4. */
  5. import { fetchApi, wrapErrorHint } from '@/utils/request';
  6. import { notify, firstCharToUpperCase } from '@/utils/tool';
  7. import { identity } from 'lodash';
  8. import Vue from 'vue';
  9. export const isClient = !!global.electron; // process.env.IS_CLIENT;
  10. const MAX_UPLOAD_ENQUEUE = 5;
  11. const MAX_DOWNLOAD_ENQUEUE = 5;
  12. let uploadingQueue = [];
  13. let uploadQueue = [];
  14. let downloadingQueue = [];
  15. let downloadQueue = [];
  16. const clearTaskQueue = () => {
  17. uploadingQueue = [];
  18. uploadQueue = [];
  19. downloadingQueue = [];
  20. downloadQueue = [];
  21. }
  22. let socket;
  23. const noop = () => {};
  24. const io = (path) => {
  25. const sk = new WebSocket(`ws://127.0.0.1:7777/${path}`);
  26. sk.on = sk.addEventListener;
  27. return sk;
  28. }
  29. const requestBySocket = (socketIns, message) => new Promise((resolve, reject) => {
  30. socketIns.on('open', () => { socketIns.send(message); });
  31. socketIns.on('message', e => {resolve(e);socketIns.close();});
  32. socketIns.on('error', e => reject(e));
  33. });
  34. const safeCall = f => isClient ? f : noop;
  35. const safeSocket = f => (...args) => socket && f(...args);
  36. const system = {
  37. isClient,
  38. init: safeCall(() => {
  39. console.log('客户端 electron API 检测:', global.electron);
  40. }),
  41. // initialSocket: () => {
  42. // socket = io('ws://10.240.5.17:8000');
  43. // // socket = io('ws://127.0.0.1:3000');
  44. // debugger;
  45. // socket.on('connect', () => { console.log('本地socket服务连接成功') });
  46. // },
  47. /**
  48. * todo
  49. * 通知登录
  50. */
  51. login: () => {
  52. },
  53. /**
  54. * todo
  55. * 通知登出
  56. */
  57. logout: () => {
  58. },
  59. /**
  60. * 通知系统进入当前的工作空间
  61. */
  62. entryProject: safeCall((projName, userId, fileChangeHandler, initHandler, errorHandler = identity) => {
  63. requestBySocket(io('init'), [userId, projName].join('|'))
  64. .then(response => initHandler(response.data));
  65. const watchSocket = io('subscriptionFileChange');
  66. watchSocket.on('open', () => {
  67. watchSocket.send(projName);
  68. });
  69. watchSocket.on('message', fileChangeHandler);
  70. watchSocket.on('error', errorHandler);
  71. return watchSocket;
  72. }),
  73. /**
  74. * todo
  75. * 离开工作空间时注销socket
  76. */
  77. leaveProject: safeCall((watchSocket) => {
  78. watchSocket.close();
  79. clearTaskQueue();
  80. }),
  81. /**
  82. * 下载文件到工作空间
  83. */
  84. downloadFile: safeCall((fileIpfsId, projectName, fileName, dirName, onProcessHandler, onErrorHandler = identity) => {
  85. const downloadTask = () => {
  86. const socket = io('download');
  87. socket.on('open', () => {
  88. socket.send([fileIpfsId, projectName, fileName, dirName].join('|'));
  89. });
  90. socket.on('message', (e) => {
  91. if(e.data === '-1') {
  92. //notify.error('文件下载失败,请检查网络。');
  93. this.$notify({
  94. message:`${fileName},网络请求失败。`,
  95. type:['warning','download'],
  96. title:"文件下载失败"
  97. })
  98. onErrorHandler(e);
  99. return;
  100. }
  101. if(e.data === '-2') {
  102. notify.error('请先关闭本地文件后再下载');
  103. onErrorHandler(e);
  104. return;
  105. }
  106. onProcessHandler(e, socket);
  107. });
  108. socket.on('error', e => {
  109. onErrorHandler(e);
  110. });
  111. socket.on('close', () => {
  112. downloadingQueue = downloadingQueue.filter(iTask => iTask !== downloadTask);
  113. if(downloadQueue.length) {
  114. const nextTask = downloadQueue.shift();
  115. downloadingQueue.push(nextTask);
  116. nextTask();
  117. }
  118. })
  119. }
  120. if(downloadingQueue.length >= MAX_DOWNLOAD_ENQUEUE) {
  121. downloadQueue.push(downloadTask);
  122. } else {
  123. downloadingQueue.push(downloadTask);
  124. downloadTask();
  125. }
  126. }),
  127. // 解析文件/文件夹路径信息
  128. // {
  129. // name: '11-28会议纪要.docx',
  130. // extension:".docx",
  131. // relativePath:"协同项目\\分析\\11-28会议纪要.docx",
  132. // absolutePath:"C:\\Users\\yuan_rh\\easycloud\\332174685636661248\\协同项目\\分析\\11-28会议纪要.docx"
  133. // }
  134. analyzeSystemPath: safeCall(async (systemFullpath) => {
  135. const response = await requestBySocket(io('getFolderFileInfo'), systemFullpath);
  136. try {
  137. const obj = JSON.parse(response.data);
  138. const uploadTasks = Object.values(obj).map((data) => {
  139. const { name: extensionedFileName, extension: dotExtension, relativePath, absolutePath } = data;
  140. const extension = dotExtension.indexOf('.') === 0 ? dotExtension.slice(1) : dotExtension;
  141. const fileName = extensionedFileName.slice(0, -dotExtension.length);
  142. return {
  143. fileName,
  144. extension,
  145. relativePath: relativePath.split('\\').filter(identity).join('/'),
  146. fullPath: absolutePath,
  147. };
  148. });
  149. return uploadTasks;
  150. } catch(e) {
  151. return [];
  152. }
  153. }),
  154. // 选择文件
  155. chooseFiles: safeCall(async () => {
  156. const { ipcRenderer } = global.electron;
  157. const res = await ipcRenderer.invoke('project-choose-files');
  158. const { canceled, filePaths } = res;
  159. if(canceled) return null;
  160. return filePaths;
  161. }),
  162. // 选择文件
  163. chooseFolders: safeCall(async () => {
  164. const { ipcRenderer } = global.electron;
  165. const res = await ipcRenderer.invoke('project-choose-folders');
  166. const { canceled, filePaths } = res;
  167. if(canceled) return null;
  168. return filePaths;
  169. }),
  170. /**
  171. * 上传文件到工作空间
  172. * 程序步骤:
  173. * + 用户选择本地文件/文件夹
  174. * + 文件上传至本地ipfs节点
  175. * + 将文件的ipfsCid连同文件信息发送到远端服务器
  176. * @param {params.projectId} 项目Id
  177. * @param {params.projectName} 项目名称
  178. * @param {params.folderId} 节点文件夹Id
  179. * @param {params.folderName} 节点文件夹Id
  180. * @param {params.folderLevelId} 节点文件夹levelId
  181. * @param {params.distFileRelativePath} 节点文件夹下的相对路径, 直接在节点文件夹下则为空, 用正斜杠
  182. * @param {params.sourceFilePath} 上传文件的完整路径
  183. * @param {params.fileName} 上传文件名
  184. * @param {params.fileExtension} 上传文件名
  185. * @param {params.fileList} 用于对比文件名是否重复的文件队列
  186. * @param {params.onSuccessHandler} 完成上传时的回调
  187. * @param {params.onProgressHandler} 上传进度反馈的回调
  188. * @param {params.onErrorHandler} 上传失败的回调
  189. */
  190. uploadFile: safeCall(async (params) => {
  191. const {
  192. projectId, projectName,
  193. folderId, folderName, folderLevelId, distFileRelativePath = '',
  194. fileName, fileExtension, sourceFilePath,
  195. fileList, onSuccess: onSuccessHandler, onProgress: onProgressHandler = identity, onError: onErrorHandler = identity,
  196. totalReadyUploadNum
  197. } = params;
  198. let {tempNumWrap} = params;//已经完成的上传文件上传数量
  199. const extensionedFileName = fileExtension ? `${fileName}.${fileExtension}`: fileName;
  200. const distFilePath = `${folderName}${distFileRelativePath ? `/${distFileRelativePath}`: ''}`;
  201. const maybeFile = fileList.find(iFile => distFileRelativePath === iFile.relativePath && `${iFile.archName}${iFile.extension ? `.${iFile.extension}` : ''}` === extensionedFileName);
  202. // 检测当前工作目录中是否存在同名文件
  203. // 考虑到需要批量的环境使用,把这块的逻辑移到页面去实现
  204. // if(maybeFile) {
  205. // let confirmRes = false;
  206. // try {
  207. // await Vue.prototype.$confirm('监测到文件夹存在同名文件,是否继续上传并覆盖同名文件?');
  208. // confirmRes = true;
  209. // } catch(e) { console.log('user canceled'); }
  210. // if(!confirmRes) return;
  211. // }
  212. const uploadFile = maybeFile
  213. ? firstCharToUpperCase({ ...maybeFile, ModifyUserId: sessionStorage.userId })
  214. : {
  215. // 文件名称 不带扩展名
  216. ArchName: fileName,
  217. // CommonStatus: 0,
  218. // CreateTime: "string",
  219. // 文件上传者Id
  220. CreateUserId: sessionStorage.userId,
  221. // Deleted: 0,
  222. Extension: fileExtension,
  223. // 文件大小 单位?
  224. // FileSize: +size,
  225. // 所处文件夹id
  226. FolderId: folderId,
  227. // 所处文件夹层级,拼接符:_
  228. FolderLevelId: folderLevelId,
  229. Id: `upload:${`${Math.random()}`.slice(2, 8)}`,
  230. // IpfsCid: hash,
  231. // IsShowRecycle: 0,
  232. // Milestone: 0,
  233. // ModifyTime: "string",
  234. // ModifyUserId: 0,
  235. // 项目id
  236. ProjId: projectId,
  237. RelativePath: distFileRelativePath,
  238. // ShowUrl: "string",
  239. Status: 2,
  240. Version: 1,
  241. // WorkStatus: 0
  242. };
  243. // 预写一条上传信息给页面
  244. onProgressHandler({ process: 0 }, uploadFile);
  245. const uploadTask = () => {
  246. const resolveError = (...args) => {
  247. uploadingQueue = uploadingQueue.filter(iTask => iTask !== uploadTask);
  248. if(uploadQueue.length) {
  249. const nextTask = uploadQueue.shift();
  250. uploadingQueue.push(nextTask);
  251. nextTask();
  252. }
  253. onErrorHandler(...args);
  254. }
  255. const resolveSuccess = (...args) => {
  256. uploadingQueue = uploadingQueue.filter(iTask => iTask !== uploadTask);
  257. if(uploadQueue.length) {
  258. const nextTask = uploadQueue.shift();
  259. uploadingQueue.push(nextTask);
  260. nextTask();
  261. }
  262. onSuccessHandler(...args);
  263. }
  264. const socket = io('upload');
  265. socket.on('open', () => {
  266. const data = [sourceFilePath, extensionedFileName, projectName, distFilePath.replace(/\//g, '\\')].join('|');
  267. socket.send(data);
  268. onProgressHandler({ process: 0 }, uploadFile);
  269. });
  270. socket.on('message', async (e) => {
  271. // console.log('receive download file message:', e);
  272. if(e.data === '-1') {
  273. notify.error('文件上传失败');
  274. resolveError(e, uploadFile);
  275. return;
  276. }
  277. if(e.data === '-2') {
  278. notify.error('请先关闭本地文件后再上传');
  279. resolveError(e, uploadFile);
  280. return;
  281. }
  282. try {
  283. const progressData = JSON.parse(e.data);
  284. const { size, process, hash } = progressData;
  285. onProgressHandler(progressData, uploadFile);
  286. if(process !== 100 || !hash) return;
  287. socket.close();
  288. // {"size":"88.69","currentSize":"88.69","unit":"KiB","process":100,"hash":""}
  289. // {"size":"","currentSize":"","unit":"","process":100,"hash":"QmPJ9i4z5UdoQpLH1DrkhZiTZra2rGicXiPabiLw4LvTmX"}
  290. uploadFile.FileSize = +size;
  291. uploadFile.IpfsCid = hash;
  292. const copyUploadFile = { ...uploadFile };
  293. if(!maybeFile) {
  294. delete copyUploadFile.Id;
  295. }
  296. const res = await fetchApi(`file/${maybeFile ? 'updateFile' : 'addFile'}`, copyUploadFile);
  297. wrapErrorHint(res);
  298. if(res.Code !== 0) {
  299. resolveError(res, uploadFile);
  300. return;
  301. }
  302. //notify.success(maybeFile ? '上传成功, 已覆盖同名文件' : '上传成功');
  303. tempNumWrap.tempNumCount += 1;
  304. // console.log('tempNumCount', tempNumWrap.tempNumCount);
  305. // console.log('totalReadyUploadNum', totalReadyUploadNum);
  306. if(tempNumWrap.tempNumCount == totalReadyUploadNum) {
  307. Vue.prototype.$notify({
  308. type:["success","upload"],
  309. title:"文件上传成功",
  310. message:`${uploadFile.ArchName}`
  311. })
  312. }
  313. resolveSuccess(uploadFile);
  314. } catch (e) {
  315. console.error('socket-upload-file parse data have error:', e);
  316. resolveError(e, uploadFile);
  317. }
  318. });
  319. socket.on('error', e => {
  320. resolveError(e, uploadFile);
  321. });
  322. }
  323. // 处理任务单元
  324. if(uploadingQueue.length >= MAX_UPLOAD_ENQUEUE) {
  325. uploadQueue.push(uploadTask);
  326. } else {
  327. uploadingQueue.push(uploadTask);
  328. uploadTask();
  329. }
  330. }),
  331. /**
  332. * 上传文件到工作空间
  333. * 程序步骤:
  334. * + 用户选择本地文件/文件夹
  335. * + 文件上传至本地ipfs节点
  336. * + 将文件的ipfsCid连同文件信息发送到远端服务器
  337. */
  338. // uploadFileOld: safeCall(async (projectId, projectName, folderId, folderName, levelId, fileList, onSuccessHandler, onProgressHandler = identity, onErrorHandler = identity) => {
  339. // const { ipcRenderer } = global.electron;
  340. // const res = await ipcRenderer.invoke('project-upload-file');
  341. // console.log('ipcRenderer project-selected-upload-file: ', res);
  342. // const { canceled, filePaths } = res;
  343. // if(canceled) return;
  344. // const filePath = filePaths[0];
  345. // const extensionedFileName = filePath.split(/\/|\\/g).pop();
  346. // const tempFilePaths = extensionedFileName.split('.');
  347. // const extension = tempFilePaths.length > 1 ? tempFilePaths.pop() : '';
  348. // const fileName = tempFilePaths.join('.');
  349. // const maybeFile = fileList.find(iFile => `${iFile.archName}${iFile.extension ? `.${iFile.extension}` : ''}` === extensionedFileName);
  350. // // 检测当前工作目录中是否存在同名文件
  351. // if(maybeFile) {
  352. // let confirmRes = false;
  353. // try {
  354. // await Vue.prototype.$confirm('监测到文件夹存在同名文件,是否继续上传并覆盖同名文件?');
  355. // confirmRes = true;
  356. // } catch(e) { console.log('user canceled'); }
  357. // if(!confirmRes) return;
  358. // }
  359. // const uploadFile = maybeFile
  360. // ? firstCharToUpperCase({ ...maybeFile, ModifyUserId: sessionStorage.userId })
  361. // : {
  362. // // 文件名称 不带扩展名
  363. // ArchName: fileName,
  364. // // CommonStatus: 0,
  365. // // CreateTime: "string",
  366. // // 文件上传者Id
  367. // CreateUserId: sessionStorage.userId,
  368. // // Deleted: 0,
  369. // Extension: extension,
  370. // // 文件大小 单位?
  371. // // FileSize: +size,
  372. // // 所处文件夹id
  373. // FolderId: folderId,
  374. // // 所处文件夹层级,拼接符:_
  375. // FolderLevelId: levelId,
  376. // Id: `upload:${`${Math.random()}`.slice(2, 8)}`,
  377. // // IpfsCid: hash,
  378. // // IsShowRecycle: 0,
  379. // // Milestone: 0,
  380. // // ModifyTime: "string",
  381. // // ModifyUserId: 0,
  382. // // 项目id
  383. // ProjId: projectId,
  384. // // ShowUrl: "string",
  385. // Status: 2,
  386. // Version: 1,
  387. // // WorkStatus: 0
  388. // };
  389. // const socket = io('upload');
  390. // socket.on('open', () => {
  391. // const data = [filePath, extensionedFileName, projectName, folderName].join('|');
  392. // socket.send(data);
  393. // onProgressHandler({ process: 0 }, uploadFile);
  394. // });
  395. // socket.on('message', async (e) => {
  396. // console.log('receive download file message:', e);
  397. // if(e.data === '-1') {
  398. // notify.error('文件上传失败,请检查网络。');
  399. // onErrorHandler(e, uploadFile);
  400. // return;
  401. // }
  402. // if(e.data === '-2') {
  403. // notify.error('请先关闭本地文件后再上传');
  404. // onErrorHandler(e, uploadFile);
  405. // return;
  406. // }
  407. // try {
  408. // const progressData = JSON.parse(e.data);
  409. // const { size, process, hash } = progressData;
  410. // onProgressHandler(progressData, uploadFile);
  411. // if(process !== 100 || !hash) return;
  412. // socket.close();
  413. // // {"size":"88.69","currentSize":"88.69","unit":"KiB","process":100,"hash":""}
  414. // // {"size":"","currentSize":"","unit":"","process":100,"hash":"QmPJ9i4z5UdoQpLH1DrkhZiTZra2rGicXiPabiLw4LvTmX"}
  415. // // const maybeFile = fileList.find(iFile => `${iFile.archName}${iFile.extension ? `.${iFile.extension}` : ''}` === extensionedFileName);
  416. // uploadFile.FileSize = +size;
  417. // uploadFile.IpfsCid = hash;
  418. // if(!maybeFile) {
  419. // delete uploadFile.Id;
  420. // }
  421. // const res = await fetchApi(`file/${maybeFile ? 'updateFile' : 'addFile'}`, uploadFile);
  422. // wrapErrorHint(res);
  423. // if(res.Code !== 0) return;
  424. // //notify.success(maybeFile ? '上传成功, 已覆盖同名文件' : '上传成功');
  425. // notify.success('文件已上传。')
  426. // onSuccessHandler(uploadFile);
  427. // } catch (e) {
  428. // console.error('socket-upload-file parse data have error:', e);
  429. // onErrorHandler(e, uploadFile);
  430. // }
  431. // });
  432. // socket.on('error', e => {
  433. // onErrorHandler(e, uploadFile);
  434. // });
  435. // }),
  436. /**
  437. * 更新本地文件
  438. */
  439. updateFile: safeCall((file, localFilePathPrefix, projectName, relativePath, onSuccessHandler, onProgressHandler = identity, onErrorHandler = identity) => {
  440. const socket = io('upload');
  441. const { archName, extension, id: fileId } = file;
  442. const extensionedFileName = `${archName}${extension ? `.${extension}` : ''}`;
  443. const filePath = `${localFilePathPrefix}\\${relativePath}\\${extensionedFileName}`;
  444. socket.on('open', () => {
  445. const data = [filePath, extensionedFileName, projectName, relativePath].join('|');
  446. socket.send(data);
  447. });
  448. socket.on('message', async (e) => {
  449. if(e.data === '-1') {
  450. notify.error('文件上传失败');
  451. onErrorHandler(e);
  452. return;
  453. }
  454. if(e.data === '-2') {
  455. notify.error('请先关闭本地文件后再上传');
  456. onErrorHandler(e);
  457. return;
  458. }
  459. try {
  460. const progressData = JSON.parse(e.data);
  461. const { size, process, hash } = progressData;
  462. onProgressHandler(progressData);
  463. if(process !== 100 || !hash) return;
  464. socket.close();
  465. const copyFile = firstCharToUpperCase({ ...file, ipfsCid: hash, size: +size, ModifyUserId: sessionStorage.userId });
  466. const res = await fetchApi('file/updateFile', copyFile);
  467. wrapErrorHint(res);
  468. // if(res.Code === 0) { notify.success(`${archName} 更新成功`); }
  469. onSuccessHandler(copyFile);
  470. return;
  471. } catch(err) {
  472. console.error('socket-update-file parse data have error:', e);
  473. // todo 上传失败
  474. }
  475. });
  476. socket.on('error', e => {
  477. onErrorHandler(e, file);
  478. });
  479. }),
  480. /**
  481. * 系统打开文件
  482. */
  483. openFile: safeCall((filePath) => {
  484. const { shell } = global.electron;
  485. shell.openPath(filePath);
  486. }),
  487. /**
  488. * 系统打开文件目录
  489. */
  490. openFolder: safeCall((filePath) => {
  491. const { shell } = global.electron;
  492. shell.showItemInFolder(filePath);
  493. }),
  494. }
  495. export default system;