feat:远程同步文件

This commit is contained in:
shixiaohua 2024-03-13 16:21:03 +08:00
parent 7bc16cbb07
commit 3a688af342
5 changed files with 194 additions and 59 deletions

View File

@ -99,6 +99,23 @@ exports.menuRebuild = (mainWindow) => {
} }
} }
] ]
},
{
label: '云服务',
submenu: [
{
label: '同步上传',
click: async () => {
await shell.openExternal('http://www.huaruyu.com')
}
},
{
label: '同步下载',
click: async () => {
await shell.openExternal('http://www.huaruyu.com')
}
}
]
} }
]; ];
} }

View File

@ -1,42 +1,27 @@
const COS = require('cos-nodejs-sdk-v5'); const COS = require('cos-nodejs-sdk-v5');
const pathOpt = require("path"); const {dialog} = require('electron')
const cos = new COS({ const {readFileSync,createWriteStream}=require('node:fs')
const md5 = require("md5");
class UploadUtils {
static cos = new COS({
SecretId: 'AKIDvjKhqrfEaliRq11nMcrGZmsATiyNl1BA', SecretId: 'AKIDvjKhqrfEaliRq11nMcrGZmsATiyNl1BA',
// 推荐使用环境变量获取;用户的 SecretId建议使用子账号密钥授权遵循最小权限指引降低使用风险。 // 推荐使用环境变量获取;用户的 SecretId建议使用子账号密钥授权遵循最小权限指引降低使用风险。
// 子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140 // 子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
SecretKey: 'xpZCjCTVJzZG2wyy8mFVwLWTVVIqAKct', SecretKey: 'xpZCjCTVJzZG2wyy8mFVwLWTVVIqAKct',
Domain: 'https://note-1324909903.cos.ap-beijing.myqcloud.com' Domain: 'https://note-1324909903.cos.ap-beijing.myqcloud.com'
}); });
const Bucket='note-1324909903' static Bucket='note-1324909903'
const Region = 'ap-beijing' static Region = 'ap-beijing'
class UploadUtils {
constructor(store) { constructor(store) {
this.store = store; this.store = store;
} }
syncActiveFile() { static selfUploadFile(activeFile){
let tableBarItem = JSON.parse(this.store.get("persist:tableBarItem"));
if (!tableBarItem) {
return;
}
let activeFile = tableBarItem.activeKey?tableBarItem.activeKey.replaceAll('"',""):undefined;
console.log("activeFile:", activeFile)
if (activeFile) {
cos.headObject({
Bucket:Bucket,
Region:Region,
// 不能以 / 开头
Key: activeFile,
}, function(err, data) {
console.log("err || data.CommonPrefixes",err || data);
if (data&&data.ETag){
// 文件存在比较MD5值
let md5= data.ETag.replaceAll('"',"")
console.log("fileList[0].ETag",md5)
}else {
console.log("cos.uploadFile") console.log("cos.uploadFile")
cos.uploadFile({ UploadUtils.cos.uploadFile({
Bucket: Bucket, /* 填入您自己的存储桶,必须字段 */ Bucket: UploadUtils.Bucket, /* 填入您自己的存储桶,必须字段 */
Region: Region, /* 存储桶所在地域,例如 ap-beijing必须字段 */ Region: UploadUtils.Region, /* 存储桶所在地域,例如 ap-beijing必须字段 */
Key: activeFile, /* 存储在桶里的对象键例如1.jpga/b/test.txt必须字段 */ Key: activeFile, /* 存储在桶里的对象键例如1.jpga/b/test.txt必须字段 */
FilePath: activeFile, /* 必须 */ FilePath: activeFile, /* 必须 */
SliceSize: 1024 * 1024 * 5, /* 触发分块上传的阈值超过5MB使用分块上传非必须 */ SliceSize: 1024 * 1024 * 5, /* 触发分块上传的阈值超过5MB使用分块上传非必须 */
@ -54,6 +39,71 @@ class UploadUtils {
console.log(err || data); console.log(err || data);
}); });
} }
static selfDownLoadFile(activeFile){
console.log("cos.downloadFile",activeFile)
UploadUtils.cos.getObject({
Bucket: UploadUtils.Bucket, /* 填入您自己的存储桶,必须字段 */
Region: UploadUtils.Region, /* 存储桶所在地域,例如 ap-beijing必须字段 */
Key: activeFile, /* 存储在桶里的对象键例如1.jpga/b/test.txt必须字段 */
Output: activeFile
// 支持自定义headers 非必须
}, function (err, data) {
console.log(err || data);
});
}
syncActiveFile() {
let tableBarItem = JSON.parse(this.store.get("persist:tableBarItem"));
if (!tableBarItem) {
return;
}
let activeFile = tableBarItem.activeKey?tableBarItem.activeKey.replaceAll('"',""):undefined;
console.log("activeFile:", activeFile)
if (activeFile) {
let dirMessage = JSON.parse(this.store.get("persist:dirMessage"));
UploadUtils.cos.headObject({
Bucket:UploadUtils.Bucket,
Region:UploadUtils.Region,
// 不能以 / 开头
Key: activeFile,
}, function(err, data) {
console.log("err || data.CommonPrefixes"+activeFile,err || data);
if (data&&data.ETag){
// 文件存在比较MD5值
let onlineMd5= data.ETag.replaceAll('"',"")
let fileMd5
if (dirMessage&&dirMessage.data&&dirMessage.data.activeFile) {
fileMd5= dirMessage.data.activeFile.fileMd5;
}
if (!fileMd5){
fileMd5=md5(readFileSync(activeFile).toString())
}
if (onlineMd5===fileMd5){
return
}
console.log("fileList[0].ETag",onlineMd5)
let number = dialog.showMessageBoxSync({
"message":"云文件已修改是否同步到本地",
"type":"info",
"buttons":["是","否"],
"defaultId":0
});
if (number===0){
UploadUtils.selfDownLoadFile(activeFile)
}else if(number===1){
if (dialog.showMessageBoxSync({
"message":"是否使用本地文件覆盖远程文件",
"type":"info",
"buttons":["是","否"],
"defaultId":0
})===0){
UploadUtils.selfUploadFile(activeFile)
}
}
}else {
UploadUtils.selfUploadFile(activeFile)
}
}); });
} }

View File

@ -11,7 +11,7 @@ import md5 from "md5"
import {message} from "antd"; import {message} from "antd";
const {ipcRenderer} = window.require('electron') const {ipcRenderer} = window.require('electron')
import "./ToobarPlugin.less" import "./ToobarPlugin.less"
import {newFileAdd} from "../../../../redux/dirMessage_reducer"; import {newFileAdd, updateFileMd5} from "../../../../redux/dirMessage_reducer";
const SaveFilePlugin=(props)=> { const SaveFilePlugin=(props)=> {
let activeKey = useSelector(state => state.tableBarItem.activeKey); let activeKey = useSelector(state => state.tableBarItem.activeKey);
const dispatch = useDispatch(); const dispatch = useDispatch();
@ -71,7 +71,8 @@ const SaveFilePlugin=(props)=> {
// 修改当前文件名 // 修改当前文件名
dispatch(updatedSavedFile({filePath: filePath})) dispatch(updatedSavedFile({filePath: filePath}))
// 文件目录更新 // 文件目录更新
dispatch(newFileAdd({fileName:getFileFullNameByPath(filePath),dirFlag:false,children:[],filePath: filePath})) dispatch(newFileAdd({fileName:getFileFullNameByPath(filePath),dirFlag:false,children:[],filePath: filePath,
fileId:filePath,fileMd5:md5(resultSave)}))
} }
}) })
return return
@ -82,20 +83,27 @@ const SaveFilePlugin=(props)=> {
const editorStateSave = {"editorState": JSON.parse(editorState)}; const editorStateSave = {"editorState": JSON.parse(editorState)};
resultSave = JSON.stringify(editorStateSave); resultSave = JSON.stringify(editorStateSave);
} }
// 后期不再读取文件直接读取文件MD5
importFile(filePath).then(value => { importFile(filePath).then(value => {
let save let save
let newFileMd5
if (props.filePath.endsWith(".md")){ if (props.filePath.endsWith(".md")){
// editorState // editorState
save = (isEmpty(value)) || md5(resultSave) !== md5(value.toString()); newFileMd5 = md5(value.toString());
save = (isEmpty(value)) || md5(resultSave) !== newFileMd5;
}else { }else {
save = (isEmpty(value)) || md5(resultSave) !== md5(JSON.stringify(JSON.parse(value.toString()))); newFileMd5 = md5(JSON.stringify(JSON.parse(value.toString())));
save = (isEmpty(value)) || md5(resultSave) !== newFileMd5;
} }
if (save) { if (save) {
overWriteFile(filePath, resultSave) overWriteFile(filePath, resultSave).then(()=>{
console.log("保存成功"+ filePath) console.log("保存成功"+ filePath)
// 修改文件Md5
dispatch(updateFileMd5({filePath,"fileMd5":newFileMd5}))
// messageApi.open({type:"success",content:"保存成功:" + filePath,duration:1}) // messageApi.open({type:"success",content:"保存成功:" + filePath,duration:1})
message.success("保存成功:" + filePath) message.success("保存成功:" + filePath)
})
} }
}).catch(error => }).catch(error =>
console.error(error) console.error(error)
@ -115,10 +123,5 @@ const SaveFilePlugin=(props)=> {
}; };
},[editor,onChange] },[editor,onChange]
) )
// return (
// <div>
// {contextHolder}
// </div>
// )
} }
export default SaveFilePlugin export default SaveFilePlugin

View File

@ -1,6 +1,12 @@
import {createSlice} from '@reduxjs/toolkit' import {createSlice} from '@reduxjs/toolkit'
import {isEmpty} from "../utils/ObjectUtils"; import {isEmpty} from "../utils/ObjectUtils";
import {fileDirFormat, filePathSplit, getFileDirByPath, getFileFullNameByPath} from "../utils/PathOperate"; import {
fileDirFormat,
filePathSplit,
getFileDirByPath,
getFileFullNameByPath,
getFileNameByPath
} from "../utils/PathOperate";
import {FileTree, insertNode, removeNode, updateNode} from "../utils/FileTree" import {FileTree, insertNode, removeNode, updateNode} from "../utils/FileTree"
import {func} from "prop-types"; import {func} from "prop-types";
/* /*
@ -19,8 +25,9 @@ import {func} from "prop-types";
export const dirMessageSlice = createSlice({ export const dirMessageSlice = createSlice({
name: 'dirMessage', name: 'dirMessage',
initialState: { initialState: {
data: [], // 暂时只存储文件信息,不存贮文件夹信息。
dirTree:new FileTree("root","/root",true,[]) data: new Map([['root','']]),
dirTree:new FileTree("root","/root",true,[],"root",undefined)
}, },
reducers: { reducers: {
dirAdd: (state, action) => { dirAdd: (state, action) => {
@ -31,6 +38,7 @@ export const dirMessageSlice = createSlice({
newFileAdd :(state,action)=>{ newFileAdd :(state,action)=>{
console.log("dirMessage:newFileAdd", state, action) console.log("dirMessage:newFileAdd", state, action)
insertNode(state.dirTree,action.payload) insertNode(state.dirTree,action.payload)
state.data.set(action.payload.fileId?action.payload.fileId:action.payload.filePath,action.payload)
}, },
dirRemove:(state,action)=>{ dirRemove:(state,action)=>{
console.log("dirMessage:dirRemove", state, action) console.log("dirMessage:dirRemove", state, action)
@ -60,6 +68,41 @@ export const dirMessageSlice = createSlice({
{"fileName":newFileName,"filePath":newFilePath,"dirFlag":false,"children":[]} {"fileName":newFileName,"filePath":newFilePath,"dirFlag":false,"children":[]}
) )
}, },
updateFileMd5:(state,action)=>{
console.log("dirMessage:updateFileMd5", state, action)
updateNode(state.dirTree,
{"filePath":action.payload.filePath},
{"fileMd5":action.payload.fileMd5})
console.log("state.data",...state.data)
let fileId = action.payload.fileId?action.payload.fileId:action.payload.filePath;
if (state.data.fileIdKey){
state.data.fileIdKey.fileMd5=action.payload.fileMd5
}else {
let newMap = new Map();
state.data.forEach((value, key) => {
newMap.set(key, value);
});
newMap.set(action.payload.filePath,{
fileTitle:getFileNameByPath(action.payload.filePath),
fileName:getFileFullNameByPath(action.payload.filePath),
fileDir:false,
children:[],
filePath:action.payload.filePath,
fileId:fileId,
fileMd5:action.payload.fileMd5
})
state.data=newMap
}
state.data.set(action.payload.filePath,{
fileTitle:getFileNameByPath(action.payload.filePath),
fileName:getFileFullNameByPath(action.payload.filePath),
fileDir:false,
children:[],
filePath:action.payload.filePath,
fileId:fileId,
fileMd5:action.payload.fileMd5
})
},
dirFileAdd:(state,action)=>{ dirFileAdd:(state,action)=>{
console.log("dirMessage:dirFileAdd", state, action) console.log("dirMessage:dirFileAdd", state, action)
let fileDir = action.payload.fileDir let fileDir = action.payload.fileDir
@ -72,6 +115,7 @@ export const dirMessageSlice = createSlice({
"children": [] "children": []
} }
insertNode(state.dirTree,fileMessage) insertNode(state.dirTree,fileMessage)
state.data.set(action.payload.fileId?action.payload.fileId:action.payload.filePath,fileMessage)
}, },
dirDirAdd:(state,action)=>{ dirDirAdd:(state,action)=>{
console.log("dirMessage:dirDirAdd", state, action) console.log("dirMessage:dirDirAdd", state, action)
@ -101,6 +145,7 @@ export const {
nextDirAdd, nextDirAdd,
dirRemove, dirRemove,
updateFileName, updateFileName,
updateFileMd5,
dirFileAdd, dirFileAdd,
dirDirAdd, dirDirAdd,
dirFileRemove, dirFileRemove,

View File

@ -3,11 +3,13 @@ import {isObject} from "formik";
import {isEmpty} from "./ObjectUtils"; import {isEmpty} from "./ObjectUtils";
export class FileTree { export class FileTree {
constructor(fileName,filePath,dirFlag,children) { constructor(fileName,filePath,dirFlag,children,fileId,fileMd5) {
this.fileName = fileName; this.fileName = fileName;
this.filePath = filePath; this.filePath = filePath;
this.dirFlag = dirFlag; this.dirFlag = dirFlag;
this.children = children; this.children = children;
this.fileId = fileId;
this.fileMd5= fileMd5;
this.pass=0 this.pass=0
this.end=0 this.end=0
} }
@ -31,7 +33,8 @@ export function insertNode(root,fileMessage) {
currentNode=find; currentNode=find;
currentNode.pass=currentNode.pass+1 currentNode.pass=currentNode.pass+1
}else { }else {
let newNode = new FileTree(fileDirSplit[i],fileDirFormatArray(fileDirSplit.slice(0,i+1)),true,[]); let newNode = new FileTree(fileDirSplit[i],fileDirFormatArray(fileDirSplit.slice(0,i+1)),true,[],
fileDirFormatArray(fileDirSplit.slice(0,i+1)),undefined);
currentNode.children.push(newNode) currentNode.children.push(newNode)
currentNode=newNode currentNode=newNode
currentNode.pass=currentNode.pass+1 currentNode.pass=currentNode.pass+1
@ -88,10 +91,27 @@ export function updateNode(root,fileMessage,newFileMessage) {
if (i===fileDirSplit.length-1) { if (i===fileDirSplit.length-1) {
let node =currentNode.children.find(file=> let node =currentNode.children.find(file=>
file.fileName===fileMessage.fileName); file.fileName===fileMessage.fileName);
if (!node){
break
}
if (newFileMessage.fileName){
node.fileName=newFileMessage.fileName node.fileName=newFileMessage.fileName
}
if (newFileMessage.filePath){
node.filePath=newFileMessage.filePath node.filePath=newFileMessage.filePath
}
if (newFileMessage.dirFlag){
node.dirFlag=newFileMessage.dirFlag node.dirFlag=newFileMessage.dirFlag
}
if (newFileMessage.children){
node.children=newFileMessage.children node.children=newFileMessage.children
} }
if (newFileMessage.fileId){
node.fileId = newFileMessage.fileId
}
if (newFileMessage.fileMd5){
node.fileMd5 = newFileMessage.fileMd5
}
}
} }
} }