rabble
rabble

Reputation: 117

Node.js moving contents of subdirectory into current directory

I have a Node script that downloads a zip into tmp/archive.zip and extracts that to tmp/archive.

I would like to move the contents of tmp/archive into .. I'm having difficulty finding how to use fs.rename in a way that is equivalent to mv tmp/archive/* .

I have tried fs.rename('tmp/archive/*', '.', function(err){ but that gives me the following error: Error: ENOENT: no such file or directory, rename 'tmp/archive/*' -> '.'

I have also tried using glob to list the contents of tmp/archive and then iterate through it and move the files using fs-extra's move, as follows:

glob('tmp/archive/*', {}, function(err, files){
    for (var i = files.length - 1; i >= 0; i--) {
      fs.move(files[i], '.', function(err){});
    }
}.bind(this));

which results in the folowing error: Error: EEXIST: file already exists, link 'tmp/archive/subdirectory' -> '.'

I could just call mv tmp/archive/* . from the script but i would like to avoid that if possible. Is there something obvious I am missing? How can I go about doing this?

Upvotes: 2

Views: 2682

Answers (1)

jfriend00
jfriend00

Reputation: 707158

Here's one way to move a directory of files from one location to another (assuming they are on the same volume and thus can be renamed rather than copied):

var Promise = require('bluebird');
var fs = Promise.promisifyAll(require('fs'));
var path = require('path');

function moveFiles(srcDir, destDir) {
    return fs.readdirAsync(srcDir).map(function(file) {
        var destFile = path.join(destDir, file);
        console.log(destFile);
        return fs.renameAsync(path.join(srcDir, file), destFile).then(function() {
            return destFile;
        });
    });
}

// sample usage:
moveFiles(path.join(".", "tempSource"), path.join(".", "tempDest")).then(function(files) {
    // all done here
}).catch(function(err) {
    // error here
});

This will move both files and sub-directories in the srcDir to destDir. Since fs.rename() will move a sub-directory all at once, you don't have to traverse recursively.

When designing a function like this, you have a choice of error behavior. The above implementation aborts upon the first error. You could change the implementation to move all files possible and then just return a list of files that could not be moved.


Here's a version that renames all files that it can and if there were any errors, it rejects at the end with a list of the files that failed and their error objects:

function moveFilesAll(srcDir, destDir) {
    return fs.readdirAsync(srcDir).map(function(file) {
        var destFile = path.join(destDir, file);
        var srcFile = path.join(srcDir, file);
        return fs.renameAsync(srcFile, destFile).then(function() {
            return {file: srcFile, err: 0};
        }).catch(function(err) {
            console.log("error on " + srcFile);
            return {file: srcFile, err: err}
        });
    }).then(function(files) {
        var errors = files.filter(function(item) {
            return item.err !== 0;
        });
        if (errors.length > 0) {
            // reject with a list of error files and their corresponding errors
            throw errors;
        }
        // for success, return list of all files moved
        return files.filter(function(item) {
            return item.file;
        });
    });
}

// sample usage:
moveFilesAll(path.join(".", "tempSource"), path.join(".", "tempDest")).then(function(files) {
    // all done here
}).catch(function(errors) {
    // list of errors here
});

Upvotes: 2

Related Questions