# io/index.js

1 | // Copyright 2013 Selenium committers
2 | // Copyright 2013 Software Freedom Conservancy
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 |
16 | var fs = require('fs'),
17 | path = require('path'),
18 | rimraf = require('rimraf'),
19 | tmp = require('tmp');
20 |
21 | var promise = require('..').promise;
22 |
23 |
24 | var PATH_SEPARATOR = process.platform === 'win32' ? ';' : ':';
25 |
26 |
27 | // PUBLIC API
28 |
29 |
30 |
31 | /**
32 | * Recursively removes a directory and all of its contents. This is equivalent
33 | * to {@code rm -rf} on a POSIX system.
34 | * @param {string} path Path to the directory to remove.
35 | * @return {!promise.Promise} A promise to be resolved when the operation has
36 | * completed.
37 | */
38 | exports.rmDir = function(path) {
39 | return new promise.Promise(function(fulfill, reject) {
40 | var numAttempts = 0;
41 | attemptRm();
42 | function attemptRm() {
43 | numAttempts += 1;
44 | rimraf(path, function(err) {
45 | if (err) {
46 | if (err.code === 'ENOTEMPTY' && numAttempts < 2) {
47 | attemptRm();
48 | return;
49 | }
50 | reject(err);
51 | } else {
52 | fulfill();
53 | }
54 | });
55 | }
56 | });
57 | };
58 |
59 |
60 | /**
61 | * Copies one file to another.
62 | * @param {string} src The source file.
63 | * @param {string} dst The destination file.
64 | * @return {!promise.Promise.<string>} A promise for the copied file's path.
65 | */
66 | exports.copy = function(src, dst) {
67 | var copied = promise.defer();
68 |
69 | var rs = fs.createReadStream(src);
70 | rs.on('error', copied.reject);
71 | rs.on('end', function() {
72 | copied.fulfill(dst);
73 | });
74 |
75 | var ws = fs.createWriteStream(dst);
76 | ws.on('error', copied.reject);
77 |
78 | rs.pipe(ws);
79 |
80 | return copied.promise;
81 | };
82 |
83 |
84 | /**
85 | * Recursively copies the contents of one directory to another.
86 | * @param {string} src The source directory to copy.
87 | * @param {string} dst The directory to copy into.
88 | * @param {(RegEx|function(string): boolean)=} opt_exclude An exclusion filter
89 | * as either a regex or predicate function. All files matching this filter
90 | * will not be copied.
91 | * @return {!promise.Promise.<string>} A promise for the destination
92 | * directory's path once all files have been copied.
93 | */
94 | exports.copyDir = function(src, dst, opt_exclude) {
95 | var predicate = opt_exclude;
96 | if (opt_exclude && typeof opt_exclude !== 'function') {
97 | predicate = function(p) {
98 | return !opt_exclude.test(p);
99 | };
100 | }
101 |
102 | // TODO(jleyba): Make this function completely async.
103 | if (!fs.existsSync(dst)) {
104 | fs.mkdirSync(dst);
105 | }
106 |
107 | var files = fs.readdirSync(src);
108 | files = files.map(function(file) {
109 | return path.join(src, file);
110 | });
111 |
112 | if (predicate) {
113 | files = files.filter(predicate);
114 | }
115 |
116 | var results = [];
117 | files.forEach(function(file) {
118 | var stats = fs.statSync(file);
119 | var target = path.join(dst, path.basename(file));
120 |
121 | if (stats.isDirectory()) {
122 | if (!fs.existsSync(target)) {
123 | fs.mkdirSync(target, stats.mode);
124 | }
125 | results.push(exports.copyDir(file, target, predicate));
126 | } else {
127 | results.push(exports.copy(file, target));
128 | }
129 | });
130 |
131 | return promise.all(results).then(function() {
132 | return dst;
133 | });
134 | };
135 |
136 |
137 | /**
138 | * Tests if a file path exists.
139 | * @param {string} path The path to test.
140 | * @return {!promise.Promise.<boolean>} A promise for whether the file exists.
141 | */
142 | exports.exists = function(path) {
143 | var result = promise.defer();
144 | fs.exists(path, result.fulfill);
145 | return result.promise;
146 | };
147 |
148 |
149 | /**
150 | * Deletes a name from the filesystem and possibly the file it refers to. Has
151 | * no effect if the file does not exist.
152 | * @param {string} path The path to remove.
153 | * @return {!promise.Promise} A promise for when the file has been removed.
154 | */
155 | exports.unlink = function(path) {
156 | return new promise.Promise(function(fulfill, reject) {
157 | fs.exists(path, function(exists) {
158 | if (exists) {
159 | fs.unlink(path, function(err) {
160 | err && reject(err) || fulfill();
161 | });
162 | } else {
163 | fulfill();
164 | }
165 | });
166 | });
167 | };
168 |
169 |
170 | /**
171 | * @return {!promise.Promise.<string>} A promise for the path to a temporary
172 | * directory.
173 | * @see https://www.npmjs.org/package/tmp
174 | */
175 | exports.tmpDir = function() {
176 | return promise.checkedNodeCall(tmp.dir);
177 | };
178 |
179 |
180 | /**
181 | * @param {{postfix: string}=} opt_options Temporary file options.
182 | * @return {!promise.Promise.<string>} A promise for the path to a temporary
183 | * file.
184 | * @see https://www.npmjs.org/package/tmp
185 | */
186 | exports.tmpFile = function(opt_options) {
187 | // |tmp.file| checks arguments length to detect options rather than doing a
188 | // truthy check, so we must only pass options if there are some to pass.
189 | return opt_options ?
190 | promise.checkedNodeCall(tmp.file, opt_options) :
191 | promise.checkedNodeCall(tmp.file);
192 | };
193 |
194 |
195 | /**
196 | * Searches the {@code PATH} environment variable for the given file.
197 | * @param {string} file The file to locate on the PATH.
198 | * @param {boolean=} opt_checkCwd Whether to always start with the search with
199 | * the current working directory, regardless of whether it is explicitly
200 | * listed on the PATH.
201 | * @return {?string} Path to the located file, or {@code null} if it could
202 | * not be found.
203 | */
204 | exports.findInPath = function(file, opt_checkCwd) {
205 | if (opt_checkCwd) {
206 | var tmp = path.join(process.cwd(), file);
207 | if (fs.existsSync(tmp)) {
208 | return tmp;
209 | }
210 | }
211 |
212 | var dirs = process.env['PATH'].split(PATH_SEPARATOR);
213 | var found = null;
214 | dirs.forEach(function(dir) {
215 | var tmp = path.join(dir, file);
216 | if (!found && fs.existsSync(tmp)) {
217 | found = tmp;
218 | }
219 | });
220 | return found;
221 | };