25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

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