Projekt

Obecné

Profil

Stáhnout (7.24 KB) Statistiky
| Větev: | Revize:
1
'use strict'
2
module.exports = copy
3
module.exports.item = copyItem
4
module.exports.recurse = recurseDir
5
module.exports.symlink = copySymlink
6
module.exports.file = copyFile
7

    
8
var nodeFs = require('fs')
9
var path = require('path')
10
var validate = require('aproba')
11
var stockWriteStreamAtomic = require('fs-write-stream-atomic')
12
var mkdirp = require('mkdirp')
13
var rimraf = require('rimraf')
14
var isWindows = require('./is-windows')
15
var RunQueue = require('run-queue')
16
var extend = Object.assign || require('util')._extend
17

    
18
function promisify (Promise, fn) {
19
  return function () {
20
    var args = [].slice.call(arguments)
21
    return new Promise(function (resolve, reject) {
22
      return fn.apply(null, args.concat(function (err, value) {
23
        if (err) {
24
          reject(err)
25
        } else {
26
          resolve(value)
27
        }
28
      }))
29
    })
30
  }
31
}
32

    
33
function copy (from, to, opts) {
34
  validate('SSO|SS', arguments)
35
  opts = extend({}, opts || {})
36

    
37
  var Promise = opts.Promise || global.Promise
38
  var fs = opts.fs || nodeFs
39

    
40
  if (opts.isWindows == null) opts.isWindows = isWindows
41
  if (!opts.Promise) opts.Promise = Promise
42
  if (!opts.fs) opts.fs = fs
43
  if (!opts.recurseWith) opts.recurseWith = copyItem
44
  if (!opts.lstat) opts.lstat = promisify(opts.Promise, fs.lstat)
45
  if (!opts.stat) opts.stat = promisify(opts.Promise, fs.stat)
46
  if (!opts.chown) opts.chown = promisify(opts.Promise, fs.chown)
47
  if (!opts.readdir) opts.readdir = promisify(opts.Promise, fs.readdir)
48
  if (!opts.readlink) opts.readlink = promisify(opts.Promise, fs.readlink)
49
  if (!opts.symlink) opts.symlink = promisify(opts.Promise, fs.symlink)
50
  if (!opts.chmod) opts.chmod = promisify(opts.Promise, fs.chmod)
51

    
52
  opts.top = from
53
  opts.mkdirpAsync = promisify(opts.Promise, mkdirp)
54
  var rimrafAsync = promisify(opts.Promise, rimraf)
55

    
56
  var queue = new RunQueue({
57
    maxConcurrency: opts.maxConcurrency,
58
    Promise: Promise
59
  })
60
  opts.queue = queue
61

    
62
  queue.add(0, copyItem, [from, to, opts])
63

    
64
  return queue.run().catch(function (err) {
65
    // if the target already exists don't clobber it
66
    if (err.code === 'EEXIST' || err.code === 'EPERM') {
67
      return passThroughError()
68
    } else {
69
      return remove(to).then(passThroughError, passThroughError)
70
    }
71
    function passThroughError () {
72
      return Promise.reject(err)
73
    }
74
  })
75

    
76
  function remove (target) {
77
    var opts = {
78
      unlink: fs.unlink,
79
      chmod: fs.chmod,
80
      stat: fs.stat,
81
      lstat: fs.lstat,
82
      rmdir: fs.rmdir,
83
      readdir: fs.readdir,
84
      glob: false
85
    }
86
    return rimrafAsync(target, opts)
87
  }
88
}
89

    
90
function copyItem (from, to, opts) {
91
  validate('SSO', [from, to, opts])
92
  var fs = opts.fs || nodeFs
93
  var Promise = opts.Promise || global.Promise
94
  var lstat = opts.lstat || promisify(Promise, fs.lstat)
95

    
96
  return lstat(to).then(function () {
97
    return Promise.reject(eexists(from, to))
98
  }, function (err) {
99
    if (err && err.code !== 'ENOENT') return Promise.reject(err)
100
    return lstat(from)
101
  }).then(function (fromStat) {
102
    var cmdOpts = extend(extend({}, opts), fromStat)
103
    if (fromStat.isDirectory()) {
104
      return recurseDir(from, to, cmdOpts)
105
    } else if (fromStat.isSymbolicLink()) {
106
      opts.queue.add(1, copySymlink, [from, to, cmdOpts])
107
    } else if (fromStat.isFile()) {
108
      return copyFile(from, to, cmdOpts)
109
    } else if (fromStat.isBlockDevice()) {
110
      return Promise.reject(eunsupported(from + " is a block device, and we don't know how to copy those."))
111
    } else if (fromStat.isCharacterDevice()) {
112
      return Promise.reject(eunsupported(from + " is a character device, and we don't know how to copy those."))
113
    } else if (fromStat.isFIFO()) {
114
      return Promise.reject(eunsupported(from + " is a FIFO, and we don't know how to copy those."))
115
    } else if (fromStat.isSocket()) {
116
      return Promise.reject(eunsupported(from + " is a socket, and we don't know how to copy those."))
117
    } else {
118
      return Promise.reject(eunsupported("We can't tell what " + from + " is and so we can't copy it."))
119
    }
120
  })
121
}
122

    
123
function recurseDir (from, to, opts) {
124
  validate('SSO', [from, to, opts])
125
  var recurseWith = opts.recurseWith || copyItem
126
  var fs = opts.fs || nodeFs
127
  var chown = opts.chown || promisify(Promise, fs.chown)
128
  var readdir = opts.readdir || promisify(Promise, fs.readdir)
129
  var mkdirpAsync = opts.mkdirpAsync || promisify(Promise, mkdirp)
130

    
131
  return mkdirpAsync(to, {fs: fs, mode: opts.mode}).then(function () {
132
    var getuid = opts.getuid || process.getuid
133
    if (getuid && opts.uid != null && getuid() === 0) {
134
      return chown(to, opts.uid, opts.gid)
135
    }
136
  }).then(function () {
137
    return readdir(from)
138
  }).then(function (files) {
139
    files.forEach(function (file) {
140
      opts.queue.add(0, recurseWith, [path.join(from, file), path.join(to, file), opts])
141
    })
142
  })
143
}
144

    
145
function copySymlink (from, to, opts) {
146
  validate('SSO', [from, to, opts])
147
  var fs = opts.fs || nodeFs
148
  var readlink = opts.readlink || promisify(Promise, fs.readlink)
149
  var stat = opts.stat || promisify(Promise, fs.symlink)
150
  var symlink = opts.symlink || promisify(Promise, fs.symlink)
151
  var Promise = opts.Promise || global.Promise
152

    
153
  return readlink(from).then(function (fromDest) {
154
    var absoluteDest = path.resolve(path.dirname(from), fromDest)
155
    // Treat absolute paths that are inside the tree we're
156
    // copying as relative. This necessary to properly support junctions
157
    // on windows (which are always absolute) but is also DWIM with symlinks.
158
    var relativeDest = path.relative(opts.top, absoluteDest)
159
    var linkFrom = relativeDest.substr(0, 2) === '..' ? fromDest : path.relative(path.dirname(from), absoluteDest)
160
    if (opts.isWindows) {
161
      return stat(absoluteDest).catch(function () { return null }).then(function (destStat) {
162
        var isDir = destStat && destStat.isDirectory()
163
        var type = isDir ? 'dir' : 'file'
164
        return symlink(linkFrom, to, type).catch(function (err) {
165
          if (type === 'dir') {
166
            return symlink(linkFrom, to, 'junction')
167
          } else {
168
            return Promise.reject(err)
169
          }
170
        })
171
      })
172
    } else {
173
      return symlink(linkFrom, to)
174
    }
175
  })
176
}
177

    
178
function copyFile (from, to, opts) {
179
  validate('SSO', [from, to, opts])
180
  var fs = opts.fs || nodeFs
181
  var writeStreamAtomic = opts.writeStreamAtomic || stockWriteStreamAtomic
182
  var Promise = opts.Promise || global.Promise
183
  var chmod = opts.chmod || promisify(Promise, fs.chmod)
184

    
185
  var writeOpts = {}
186
  var getuid = opts.getuid || process.getuid
187
  if (getuid && opts.uid != null && getuid() === 0) {
188
    writeOpts.chown = {
189
      uid: opts.uid,
190
      gid: opts.gid
191
    }
192
  }
193

    
194
  return new Promise(function (resolve, reject) {
195
    var errored = false
196
    function onError (err) {
197
      errored = true
198
      reject(err)
199
    }
200
    fs.createReadStream(from)
201
      .once('error', onError)
202
      .pipe(writeStreamAtomic(to, writeOpts))
203
      .once('error', onError)
204
      .once('close', function () {
205
        if (errored) return
206
        if (opts.mode != null) {
207
          resolve(chmod(to, opts.mode))
208
        } else {
209
          resolve()
210
        }
211
      })
212
  })
213
}
214

    
215
function eexists (from, to) {
216
  var err = new Error('Could not move ' + from + ' to ' + to + ': destination already exists.')
217
  err.code = 'EEXIST'
218
  return err
219
}
220

    
221
function eunsupported (msg) {
222
  var err = new Error(msg)
223
  err.code = 'EUNSUPPORTED'
224
  return err
225
}
(3-3/5)