解决Hexo ftpsync卡上传

  • ~23.38K 字
  1. 1. 说明
  2. 2. 解决办法
  3. 3. 参考

说明

ftpsync 老是卡 Committing,找了一圈终于发现临时解决办法

解决办法

先安装

npm install --save hexo-deployer-ftpsync

讲 node_modules/ftpsync/lib/ftpsync.js 文件进行替换

// Design Notes
// ----------------------------------------------------------------------------
// TODO:
// - add verify() function
// - add touch() function
// - finish implementing timeSync()
// - fix time comparison on isModified

// Setup
// ----------------------------------------------------------------------------
var fs = require('fs');
var path = require('path');
var jsftp = require('jsftp');
var async = require('async');
var Client = require('ftp');
var minimatch = require('minimatch');
var config = {};
if (fs.existsSync(process.cwd() + '/config.json')) {
    config = require(process.cwd() + '/config.json');
}

var ftpc = new Client();

var settings;
var ftp;
var sync = (exports = {
    settings: {
        host: config.host,
        port: config.port || 21,
        user: config.user,
        pass: config.pass,
        local: config.local || process.cwd(),
        remote: config.remote || '/',
        ignore: config.ignore || {},
        connections: config.connections || 1,
        ltimeOffset: 0,
        rtimeOffset: 0,
    },

    log: {
        verbose: config.verbose || false,
        write: config.log || console.log,
        info: config.info || console.info,
        error: config.error || console.error,
        warn: config.warn || console.warn,
    },

    local: [],
    remote: [],
    mkdir: [],
    rmdir: [],
    add: [],
    update: [],
    remove: [],

    setup: function (callback) {
        settings = sync.settings;
        sync.log.write('Setup');
        if (sync.log.verbose) {
            sync.log.write('-------------------------------------------------------------');
        }
        // check the inputs
        if (!settings.host) {
            sync.log.warn('Host name not set');
        }
        if (!settings.user) {
            sync.log.warn('User name not set');
        }
        if (!settings.pass) {
            sync.log.warn('Password not set');
        }

        // sensible defaults
        settings.ignore = settings.ignore || [];
        settings.connections = settings.connections || 1;

        // display the settings
        if (sync.log.verbose) {
            sync.log.write('Settings:');
            sync.log.write(settings);
            sync.log.write('');
        }
        // create the ftp instance
        try {
            ftp = new jsftp({
                host: settings.host,
                port: settings.port,
                user: settings.user,
                pass: settings.pass,
            });
            ftpc.connect({
                host: settings.host,
                user: settings.user,
                password: settings.pass,
                port: settings.port,
            });
        } catch (err) {
            sync.log.error('Setup failed.');
            sync.log.write('');
            return callback(err);
        }
        sync.log.info('Setup complete.');
        sync.log.write('');
        callback(null);
    },

    collect: function (callback) {
        sync.log.write('Collecting');
        if (sync.log.verbose) {
            sync.log.write('-------------------------------------------------------------');
        }
        // fetch the file listings
        async.series(
            [
                function (callback) {
                    utils.walkLocal(settings.local, callback);
                },
                function (callback) {
                    utils.walkRemote(settings.remote, callback);
                },
            ],
            function (err, results) {
                if (err) {
                    sync.log.error('Collection failed.');
                    sync.log.write('');
                    return callback(err);
                }
                // store the values for later
                sync.local = results[0];
                sync.remote = results[1];
                if (sync.log.verbose) {
                    sync.log.write('Local:');
                    sync.log.write(sync.local);
                    sync.log.write('');
                    sync.log.write('Remote:');
                    sync.log.write(sync.remote);
                    sync.log.write('');
                }
                sync.log.info('Collection complete.');
                sync.log.write('');
                callback(null);
            }
        );
    },

    consolidate: function (callback) {
        sync.log.write('Consolidating');
        if (sync.log.verbose) {
            sync.log.write('-------------------------------------------------------------');
        }
        // prepare the directory lists for comparison
        var rdirs = sync.remote.dirs;
        var ldirs = sync.local.dirs;
        // prepare the files lists for comparison
        var rFiles = helpers.lookupTable(sync.remote.files);
        var lFiles = helpers.lookupTable(sync.local.files);
        // compare directories for modifications
        rdirs.forEach(function (dir) {
            // if a match is found
            var lIDX = ldirs.indexOf(dir);
            if (lIDX !== -1) {
                var rIDX = rdirs.indexOf(dir);
                ldirs[lIDX] = '';
                rdirs[rIDX] = '';
            }
        });
        // compare files for modifications
        rFiles.forEach(function (file) {
            var lIDX = lFiles.indexOf(file);
            // if a match is found
            if (lIDX !== -1) {
                var rIDX = rFiles.indexOf(file);
                var lFile = sync.local.files[lIDX];
                var rFile = sync.remote.files[rIDX];
                if (helpers.isDifferent(sync.local.files[lIDX], sync.remote.files[rIDX])) {
                    sync.update.push(file);
                }
                // mark updates as processed
                lFiles[lIDX] = '';
                rFiles[rIDX] = '';
            }
        });
        // process the rest
        ldirs.forEach(function (dir) {
            if (dir === '') {
                return;
            }
            sync.mkdir.push(dir);
        });
        rdirs = rdirs.reverse();
        rdirs.forEach(function (dir) {
            if (dir === '') {
                return;
            }
            sync.rmdir.push(dir);
        });
        lFiles.forEach(function (file) {
            if (file === '') {
                return;
            }
            sync.add.push(file);
        });
        rFiles.forEach(function (file) {
            if (file === '') {
                return;
            }
            sync.remove.push(file);
        });

        // log the results
        if (sync.log.verbose) {
            sync.log.write('Mkdir:');
            sync.log.write(sync.mkdir);
            sync.log.write('');
            sync.log.write('Rmdir:');
            sync.log.write(sync.rmdir);
            sync.log.write('');
            sync.log.write('Add:');
            sync.log.write(sync.add);
            sync.log.write('');
            sync.log.write('Updates:');
            sync.log.write(sync.update);
            sync.log.write('');
            sync.log.write('Remove:');
            sync.log.write(sync.remove);
            sync.log.write('');
        }
        sync.log.info('Consolidation complete.');
        sync.log.write('');
        callback(null);
    },

    commit: function (callback) {
        sync.log.write('Committing');
        if (sync.log.verbose) {
            sync.log.write('-------------------------------------------------------------');
        }
        async.series(
            [
                // add directories
                function (callback) {
                    if (sync.mkdir.length === 0) {
                        callback(null, 'no mkdirs');
                        return;
                    }
                    async.mapLimit(sync.mkdir, 1, utils.mkdir, function (err) {
                        if (err) {
                            sync.log.error('MKDIRs failed.');
                            return callback(err);
                        }
                        sync.log.info('MKDIRs complete.');
                        callback(null);
                    });
                },
                // add files
                function (callback) {
                    if (sync.add.length === 0) {
                        callback(null, 'no additions');
                        return;
                    }
                    async.mapLimit(sync.add, settings.connections, utils.upload, function (err) {
                        if (err) {
                            sync.log.error('Additions failed.');
                            return callback(err);
                        }
                        sync.log.info('Additions complete.');
                        callback(null);
                    });
                },
                // update files
                function (callback) {
                    if (sync.update.length === 0) {
                        callback(null, 'no updates');
                        return;
                    }
                    async.mapLimit(sync.update, settings.connections, utils.upload, function (err) {
                        if (err) {
                            sync.log.error('Updates failed.');
                            return callback(err);
                        }
                        sync.log.info('Updates complete.');
                        callback(null);
                    });
                },
                // remove files
                function (callback) {
                    if (sync.remove.length === 0) {
                        callback(null, 'no removals');
                        return;
                    }
                    async.mapLimit(sync.remove, 1, utils.remove, function (err) {
                        if (err) {
                            sync.log.error('Removals failed.');
                            return callback(err);
                        }
                        sync.log.info('Removals complete');
                        callback(null);
                    });
                },
                // remove directories
                function (callback) {
                    if (sync.rmdir.length === 0) {
                        callback(null, 'no rmdirs');
                        return;
                    }
                    async.mapLimit(sync.rmdir, 1, utils.rmdir, function (err) {
                        if (err) {
                            sync.log.error('RMDIRs failed.');
                            return callback(err);
                        }
                        sync.log.info('RMDIRs complete.');
                        callback(null);
                    });
                },
            ],
            function (err, results) {
                if (err) {
                    sync.log.error('Commit failed.');
                    return callback(err);
                }
                sync.log.info('Commit complete.');
                callback(null);
            }
        );
    },

    run: function (callback) {
        async.series(
            [
                // setup
                function (callback) {
                    sync.setup(callback);
                },
                // collect
                function (callback) {
                    sync.collect(callback);
                },
                // consolidate
                function (callback) {
                    sync.consolidate(callback);
                },
                // commit
                function (callback) {
                    sync.commit(callback);
                },
            ],
            function (err, results) {
                if (err) {
                    if (callback) {
                        callback(err);
                    } else {
                        sync.log.error(err);
                        process.exit();
                    }
                } else {
                    if (callback) {
                        callback(null);
                    } else {
                        process.exit();
                    }
                }
            }
        );
    },
});

var utils = (exports.utils = {
    walkLocal: function (dir, callback) {
        var dirs = [];
        var files = [];
        // walk the directory
        fs.readdir(dir, function (err, list) {
            if (err) {
                sync.log.error('fs.readdir failed.');
                return callback(err);
            }
            var i = 0;
            (function next() {
                var file = list[i++];
                // exit if all files are processed
                if (!file) {
                    return callback(null, { dirs: dirs, files: files });
                }
                // get file/dir name/stats
                var path = dir + '/' + file;
                // skip ignore files
                if (helpers.isIgnored(helpers.trimPathRoot(settings.local, path))) {
                    next();
                    return;
                }
                fs.stat(path, function (err, stat) {
                    if (err) {
                        sync.log.error('fs.stat failed.');
                        return callback(err);
                    }
                    // handle directories
                    if (stat.isDirectory()) {
                        // add the directory to the results
                        dirs.push(helpers.trimPathRoot(settings.local, path));
                        // concat results from recursive calls
                        utils.walkLocal(path, function (err, res) {
                            // recurse & shit
                            dirs = dirs.concat(res.dirs);
                            files = files.concat(res.files);
                            next();
                        });
                        return;
                    }
                    // handle files
                    if (stat.isFile()) {
                        files.push({
                            id: helpers.trimPathRoot(settings.local, path),
                            size: stat.size,
                            time: new Date(stat.ctime),
                        });
                        next();
                        return;
                    }
                    // skip everything else
                    else {
                        next();
                    }
                });
            })();
        });
    },

    walkRemote: function (dir, callback) {
        var dirs = [];
        var files = [];
        // walk the directory
        ftp.ls(dir, function (err, list) {
            if (err) {
                sync.log.error('ftp.ls failed.');
                return callback(err);
            }
            var i = 0;
            (function next() {
                var file = list[i++];
                // exit if all files are processed
                if (!file) {
                    return callback(null, { dirs: dirs, files: files });
                }
                // get file/dir name/stats
                var path = dir + '/' + file.name;
                // skip ignore files
                if (helpers.isIgnored(helpers.trimPathRoot(settings.remote, path))) {
                    next();
                    return;
                }
                // handle directories
                if (file.type === 1) {
                    // add the directory to the results
                    dirs.push(helpers.trimPathRoot(settings.remote, path));
                    // concat results from recursive calls
                    utils.walkRemote(path, function (err, res) {
                        // recurse & shit
                        dirs = dirs.concat(res.dirs);
                        files = files.concat(res.files);
                        next();
                    });
                    return;
                }
                // handle files
                if (file.type === 0) {
                    // add the file to the results
                    files.push({
                        id: helpers.trimPathRoot(settings.remote, path),
                        size: +file.size,
                        time: new Date(file.time),
                    });
                    next();
                    return;
                }
                // skip everything else (ex sumlinks)
                else {
                    next();
                }
            })();
        });
    },

    mkdir: function (dir, callback) {
        dir = settings.remote + dir;
        ftp.raw.mkd(dir, function (err, data) {
            if (err) {
                sync.log.error('MKDIR failed.');
                return callback(err);
            }
            if (sync.log.verbose) {
                sync.log.write('-', dir, 'created successfuly');
            }
            callback();
        });
    },

    rmdir: function (dir, callback) {
        dir = settings.remote + dir;
        ftp.raw.rmd(dir, function (err, data) {
            if (err) {
                sync.log.error('RMDIR failed.');
                return callback(err);
            }
            if (sync.log.verbose) {
                sync.log.write('-', dir, 'deleted successfuly');
            }
            callback();
        });
    },

    // upload a file to the remote server
    upload: function (file, callback) {
        /*var ftp = new jsftp({
      host: settings.host,
      port: settings.port,
      user: settings.user,
      pass: settings.pass
    });*/
        var local = settings.local + file;
        var remote = settings.remote + file;
        ftpc.put(local, remote, function (err) {
            if (err) {
                sync.log.error('ftp.put failed.');
                sync.log.error(err);
                return callback(err);
            }
            if (sync.log.verbose) {
                sync.log.write('-', file, 'uploaded successfuly');
            }
            callback();
        });
    },

    remove: function (file, callback) {
        file = settings.remote + file;
        ftp.raw.dele(file, function (err, data) {
            if (err) {
                sync.log.error('Remove failed.');
                return callback(err);
            }
            if (sync.log.verbose) {
                sync.log.write('-', file, 'deleted successfuly');
            }
            callback();
        });
        ftpc.end();
    },
});

var helpers = {
    // synchronizes the local and remote clocks
    syncTime: function (ltime, rtime) {
        // get the current date/time
        var now = new Date();
        // calculate the local time offset
        var ltimeOffset = now.getTimezoneOffset() / 60;
        // round to the nearest hour
        var coeff = 1000 * 60 * 60;
        // greenwich mean time
        var gmtimeMM = new Date((Math.round(ltime / coeff) - ltimeOffset) * coeff);
        // local time
        var ltimeMM = new Date(Math.round(ltime / coeff) * coeff);
        // remote time
        var rtimeMM = new Date(Math.round(rtime / coeff) * coeff);
        // calculate the remote time offset
        var rtimeOffset = (rtimeMM - ltimeMM) / coeff;
        console.log('Offsets:');
        console.log('loffset: +' + ltimeOffset);
        console.log('roffset: +' + rtimeOffset);
    },

    // maps a file lookup table from an array of file objects
    lookupTable: function (array) {
        //if (!array) { return []; }
        var lookup = [];
        for (var i = 0, len = array.length; i < len; i++) {
            lookup[i] = array[i].id;
        }
        return lookup;
    },

    // trims the base dir of from the file path
    trimPathRoot: function (root, path) {
        var rdirs = root.split('/');
        var fdirs = path.split('/');
        return '/' + fdirs.splice(rdirs.length, fdirs.length - rdirs.length).join('/');
    },

    // compare local vs remote file sizes
    isDifferent: function (lfile, rfile) {
        return lfile.size !== rfile.size;
    },

    // compare a local vs remote file for modification
    isModified: function (lfile, rfile) {
        // round to the nearest minute
        //var minutes = 1000 * 60;
        //var hours = 1000 * 60 * 60;
        //var ltime = new Date(((Math.round(ltime / minutes) * minutes) - (ltimeOffset * hours)));
        //var rtime = new Date(((Math.round(rtime / minutes) * minutes) - (rtimeOffset * hours)));
        //var ltime = new Date(Math.round(ltime / minutes) * minutes);
        //var rtime = new Date(Math.round(rtime / minutes) * minutes);
        //console.log('Compare:');
        //console.log('lTime: ' + ltime);
        //console.log('rTime: ' + rtime);
    },

    isIgnored: function (path) {
        // skip if no ignores are defined
        if (settings.ignore.length === 0) {
            return false;
        }
        // should the path be ignored?
        for (var i = 0, len = settings.ignore.length; i < len; i++) {
            if (minimatch(path, settings.ignore[i], { matchBase: true })) {
                return true;
            }
        }
        return false;
    },
};

module.exports = exports;

并在项目根目录执行下面命令

npm install ftp --save

参考

https://es2q.com/blog/2019/05/12/Hexo-ftpsync-bug/