feat:远程同步文件
This commit is contained in:
parent
7bc16cbb07
commit
3a688af342
|
@ -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')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,58 @@
|
||||||
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')
|
||||||
SecretId: 'AKIDvjKhqrfEaliRq11nMcrGZmsATiyNl1BA',
|
const md5 = require("md5");
|
||||||
// 推荐使用环境变量获取;用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。
|
|
||||||
// 子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
|
|
||||||
SecretKey: 'xpZCjCTVJzZG2wyy8mFVwLWTVVIqAKct',
|
|
||||||
Domain: 'https://note-1324909903.cos.ap-beijing.myqcloud.com'
|
|
||||||
});
|
|
||||||
const Bucket='note-1324909903'
|
|
||||||
const Region = 'ap-beijing'
|
|
||||||
class UploadUtils {
|
class UploadUtils {
|
||||||
|
|
||||||
|
static cos = new COS({
|
||||||
|
SecretId: 'AKIDvjKhqrfEaliRq11nMcrGZmsATiyNl1BA',
|
||||||
|
// 推荐使用环境变量获取;用户的 SecretId,建议使用子账号密钥,授权遵循最小权限指引,降低使用风险。
|
||||||
|
// 子账号密钥获取可参考https://cloud.tencent.com/document/product/598/37140
|
||||||
|
SecretKey: 'xpZCjCTVJzZG2wyy8mFVwLWTVVIqAKct',
|
||||||
|
Domain: 'https://note-1324909903.cos.ap-beijing.myqcloud.com'
|
||||||
|
});
|
||||||
|
static Bucket='note-1324909903'
|
||||||
|
static Region = 'ap-beijing'
|
||||||
|
|
||||||
constructor(store) {
|
constructor(store) {
|
||||||
this.store = store;
|
this.store = store;
|
||||||
}
|
}
|
||||||
|
static selfUploadFile(activeFile){
|
||||||
|
console.log("cos.uploadFile")
|
||||||
|
UploadUtils.cos.uploadFile({
|
||||||
|
Bucket: UploadUtils.Bucket, /* 填入您自己的存储桶,必须字段 */
|
||||||
|
Region: UploadUtils.Region, /* 存储桶所在地域,例如 ap-beijing,必须字段 */
|
||||||
|
Key: activeFile, /* 存储在桶里的对象键(例如1.jpg,a/b/test.txt),必须字段 */
|
||||||
|
FilePath: activeFile, /* 必须 */
|
||||||
|
SliceSize: 1024 * 1024 * 5, /* 触发分块上传的阈值,超过5MB使用分块上传,非必须 */
|
||||||
|
onTaskReady: function (taskId) { /* 非必须 */
|
||||||
|
console.log(taskId);
|
||||||
|
},
|
||||||
|
onProgress: function (progressData) { /* 非必须 */
|
||||||
|
console.log(JSON.stringify(progressData));
|
||||||
|
},
|
||||||
|
onFileFinish: function (err, data, options) { /* 非必须 */
|
||||||
|
console.log(options.Key + '上传' + (err ? '失败' : '完成'));
|
||||||
|
},
|
||||||
|
// 支持自定义headers 非必须
|
||||||
|
}, function (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.jpg,a/b/test.txt),必须字段 */
|
||||||
|
Output: activeFile
|
||||||
|
// 支持自定义headers 非必须
|
||||||
|
}, function (err, data) {
|
||||||
|
console.log(err || data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
syncActiveFile() {
|
syncActiveFile() {
|
||||||
let tableBarItem = JSON.parse(this.store.get("persist:tableBarItem"));
|
let tableBarItem = JSON.parse(this.store.get("persist:tableBarItem"));
|
||||||
if (!tableBarItem) {
|
if (!tableBarItem) {
|
||||||
|
@ -21,38 +61,48 @@ class UploadUtils {
|
||||||
let activeFile = tableBarItem.activeKey?tableBarItem.activeKey.replaceAll('"',""):undefined;
|
let activeFile = tableBarItem.activeKey?tableBarItem.activeKey.replaceAll('"',""):undefined;
|
||||||
console.log("activeFile:", activeFile)
|
console.log("activeFile:", activeFile)
|
||||||
if (activeFile) {
|
if (activeFile) {
|
||||||
cos.headObject({
|
let dirMessage = JSON.parse(this.store.get("persist:dirMessage"));
|
||||||
Bucket:Bucket,
|
UploadUtils.cos.headObject({
|
||||||
Region:Region,
|
Bucket:UploadUtils.Bucket,
|
||||||
|
Region:UploadUtils.Region,
|
||||||
// 不能以 / 开头
|
// 不能以 / 开头
|
||||||
Key: activeFile,
|
Key: activeFile,
|
||||||
}, function(err, data) {
|
}, function(err, data) {
|
||||||
console.log("err || data.CommonPrefixes",err || data);
|
console.log("err || data.CommonPrefixes"+activeFile,err || data);
|
||||||
if (data&&data.ETag){
|
if (data&&data.ETag){
|
||||||
// 文件存在,比较MD5值
|
// 文件存在,比较MD5值
|
||||||
let md5= data.ETag.replaceAll('"',"")
|
let onlineMd5= data.ETag.replaceAll('"',"")
|
||||||
console.log("fileList[0].ETag",md5)
|
let fileMd5
|
||||||
}else {
|
if (dirMessage&&dirMessage.data&&dirMessage.data.activeFile) {
|
||||||
console.log("cos.uploadFile")
|
fileMd5= dirMessage.data.activeFile.fileMd5;
|
||||||
cos.uploadFile({
|
}
|
||||||
Bucket: Bucket, /* 填入您自己的存储桶,必须字段 */
|
if (!fileMd5){
|
||||||
Region: Region, /* 存储桶所在地域,例如 ap-beijing,必须字段 */
|
fileMd5=md5(readFileSync(activeFile).toString())
|
||||||
Key: activeFile, /* 存储在桶里的对象键(例如1.jpg,a/b/test.txt),必须字段 */
|
}
|
||||||
FilePath: activeFile, /* 必须 */
|
if (onlineMd5===fileMd5){
|
||||||
SliceSize: 1024 * 1024 * 5, /* 触发分块上传的阈值,超过5MB使用分块上传,非必须 */
|
return
|
||||||
onTaskReady: function (taskId) { /* 非必须 */
|
}
|
||||||
console.log(taskId);
|
console.log("fileList[0].ETag",onlineMd5)
|
||||||
},
|
let number = dialog.showMessageBoxSync({
|
||||||
onProgress: function (progressData) { /* 非必须 */
|
"message":"云文件已修改是否同步到本地",
|
||||||
console.log(JSON.stringify(progressData));
|
"type":"info",
|
||||||
},
|
"buttons":["是","否"],
|
||||||
onFileFinish: function (err, data, options) { /* 非必须 */
|
"defaultId":0
|
||||||
console.log(options.Key + '上传' + (err ? '失败' : '完成'));
|
|
||||||
},
|
|
||||||
// 支持自定义headers 非必须
|
|
||||||
}, function (err, data) {
|
|
||||||
console.log(err || data);
|
|
||||||
});
|
});
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
// messageApi.open({type:"success",content:"保存成功:" + filePath,duration:1})
|
// 修改文件Md5
|
||||||
message.success("保存成功:" + filePath)
|
dispatch(updateFileMd5({filePath,"fileMd5":newFileMd5}))
|
||||||
|
// messageApi.open({type:"success",content:"保存成功:" + filePath,duration:1})
|
||||||
|
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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
node.fileName=newFileMessage.fileName
|
if (!node){
|
||||||
node.filePath=newFileMessage.filePath
|
break
|
||||||
node.dirFlag=newFileMessage.dirFlag
|
}
|
||||||
node.children=newFileMessage.children
|
if (newFileMessage.fileName){
|
||||||
|
node.fileName=newFileMessage.fileName
|
||||||
|
}
|
||||||
|
if (newFileMessage.filePath){
|
||||||
|
node.filePath=newFileMessage.filePath
|
||||||
|
}
|
||||||
|
if (newFileMessage.dirFlag){
|
||||||
|
node.dirFlag=newFileMessage.dirFlag
|
||||||
|
}
|
||||||
|
if (newFileMessage.children){
|
||||||
|
node.children=newFileMessage.children
|
||||||
|
}
|
||||||
|
if (newFileMessage.fileId){
|
||||||
|
node.fileId = newFileMessage.fileId
|
||||||
|
}
|
||||||
|
if (newFileMessage.fileMd5){
|
||||||
|
node.fileMd5 = newFileMessage.fileMd5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue