1. 程式人生 > 實用技巧 >Tools/檔案同步助手

Tools/檔案同步助手

檔案同步助手(本地)

檔案

  • sync.js
const fs=require('fs');
const crypto=require('crypto');

let config,to_config,from_config;

function test_ignore(path){
    if(config.ignore){
        for(let reg of config.ignore){
            if(reg.test(path)){
                return true;
            }
        }
    }
    return false;
}

function get_keyword(path){
    if(!config.type){
        return 0;
    }
    return new Promise((resolve)=>{
        let file=fs.createReadStream(path);
        let hash=crypto.createHash(config.type);
        file.on('data',(data)=>{
            hash.update(data);
        });
        file.on('end',()=>{
            resolve(hash.digest('hex'));
        });
    });
}

async function scan_path(base,path=''){
    process.title=`${base}${path}`;
    let res={
        d:{},
        f:{}
    };
    let now_list=fs.readdirSync(`${base}${path}`);
    for(let now_path of now_list){
        if(test_ignore(`${base}${path}/${now_path}`)){
            continue;
        }
        if(fs.statSync(`${base}${path}/${now_path}`).isDirectory()){
            res.d[now_path]=await scan_path(base,`${path}/${now_path}`);
        }
        else{
            res.f[now_path]=await get_keyword(`${base}${path}/${now_path}`);
        }
    }
    return res;
}

function remove_dir(base,path,res){
    for(let dir in res.d){
        remove_dir(base,`${path}/${dir}`,res.d[dir]);
        fs.rmdirSync(`${base}${path}/${dir}`);
    }
    for(let file in res.f){
        fs.unlinkSync(`${base}${path}/${file}`);
    }
}

async function compare_path(from,to,path=''){
    //-- compare to
    for(let dir in to.d){
        if(from.d[dir]){
            compare_path(from.d[dir],to.d[dir],`${path}/${dir}`);
        }
        else{
            remove_dir(config.to,`${path}/${dir}`,to.d[dir]);
            console.log(`-td`,`${config.to}${path}/${dir}`);
            fs.rmdirSync(`${config.to}${path}/${dir}`);
            delete to.d[dir];
        }
    }
    for(let file in to.f){
        if(!from.f[file]){
            console.log(`-tf`,`${config.to}${path}/${file}`);
            fs.unlinkSync(`${config.to}${path}/${file}`);
            delete to.f[file];
        }
    }
    //-- compare from
    for(let dir in from.d){
        if(!to.d[dir]){
            console.log(`+fd`,`${config.to}${path}/${dir}`);
            fs.mkdirSync(`${config.to}${path}/${dir}`);
            to.d[dir]={
                d:{},
                f:{}
            };
        }
        compare_path(from.d[dir],to.d[dir],`${path}/${dir}`);
    }
    for(let file in from.f){
        if(to.f[file]&&to.f[file]!=from.f[file]){
            console.log(`-ff`,`${config.to}${path}/${file}`);
            fs.unlinkSync(`${config.to}${path}/${file}`);
            delete to.f[file];
        }
        if(!to.f[file]){
            console.log(`+ff`,`${config.to}${path}/${file}`);
            fs.copyFileSync(`${config.from}${path}/${file}`,`${config.to}${path}/${file}`);
            to.f[file]=from.f[file];
        }
    }
}

async function __main(){
    const config_path=process.argv[2].replace(/\\/g,'/');

    if(!fs.existsSync(config_path)){
        throw new Error(`no such config file`);
    }

    config=JSON.parse(fs.readFileSync(config_path));

    const to_config_path=`${config.to}/sync_config.json`;

    if(config.ignore){
        for(let i=0;i<config.ignore.length;i++){
            config.ignore[i]=new RegExp(...config.ignore[i]);
        }
    }

    from_config=await scan_path(config.from);
    if(fs.existsSync(to_config_path)){
        to_config=JSON.parse(fs.readFileSync(to_config_path));
    }
    else{
        to_config=await scan_path(config.to);
    }

    compare_path(from_config,to_config);

    fs.writeFileSync(to_config_path,JSON.stringify(to_config));
}
__main();
  • sync.bat //啟動器
node sync.js %*
pause
  • config.json //例子
{
    "from":"C:/Users/fhs/Documents/tools-fl",
    "to":"R:/dev",
    "ignore":[
        [".vscode"],
        ["node_modules"]
    ],
    "type":"md5"
}

config.json

from //要備份的資料夾(必須)
to //要備份的目標目錄(必須)
ignore //匹配忽略的資料夾
[
    ["www\\.\d+","ig"]
]
type //hash模式 不填寫則直接比較檔案