Skip to content

Commit 1f61819

Browse files
storage: add deleteFiles()
1 parent c7bcdc8 commit 1f61819

3 files changed

Lines changed: 235 additions & 27 deletions

File tree

lib/storage/bucket.js

Lines changed: 112 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,117 @@ Bucket.prototype.delete = function(callback) {
310310
this.makeReq_('DELETE', '', null, true, callback);
311311
};
312312

313+
/**
314+
* Iterate over the bucket's files, calling `file.delete()` on each.
315+
*
316+
* Operations are performed in parallel, up to 10 at once. The first error
317+
* breaks the loop, and will execute the provided callback with it. Specify
318+
* `{ force: true }` to suppress the errors.
319+
*
320+
* The `query` object passed as the first argument will also be passed to
321+
* {module:storage/bucket#getFiles}.
322+
*
323+
* @param {object} query - Query object. See {module:storage/bucket#getFiles}
324+
* for all of the supported properties.
325+
* @param {boolean} query.force - Supress errors until all files have been
326+
* processed.
327+
* @param {function} callback - The callback function.
328+
*
329+
* @example
330+
* //-
331+
* // Delete all of the files in the bucket.
332+
* //-
333+
* bucket.deleteFiles(function(err) {});
334+
*
335+
* //-
336+
* // By default, if a file cannot be deleted, this method will stop deleting
337+
* // files from your bucket. You can override this setting with `force: true`.
338+
* //-
339+
* bucket.deleteFiles({
340+
* force: true
341+
* }, function(errors) {
342+
* // `errors`:
343+
* // Array of errors if any occurred, otherwise null.
344+
* });
345+
*
346+
* //-
347+
* // The first argument to this method acts as a query to
348+
* // {module:storage/bucket#getFiles}. As an example, you can delete files
349+
* // which match a prefix.
350+
* //-
351+
* bucket.deleteFiles({
352+
* prefix: 'images/'
353+
* }, function(err) {
354+
* if (!err) {
355+
* // All files in the `images` directory have been deleted.
356+
* }
357+
* });
358+
*/
359+
Bucket.prototype.deleteFiles = function(query, callback) {
360+
if (util.is(query, 'function')) {
361+
callback = query;
362+
query = {};
363+
}
364+
365+
query = query || {};
366+
367+
var self = this;
368+
369+
var MAX_PARALLEL_LIMIT = 10;
370+
var errors = [];
371+
372+
// Start deleting files, iteratively fetching more as necessary.
373+
deleteFiles(query, function(err) {
374+
if (err || errors.length > 0) {
375+
callback(err || errors);
376+
return;
377+
}
378+
379+
callback(null);
380+
});
381+
382+
function deleteFiles(query, callback) {
383+
self.getFiles(query, function(err, files, nextQuery) {
384+
if (err) {
385+
callback(err);
386+
return;
387+
}
388+
389+
// Iterate through each file and make it public or private.
390+
async.eachLimit(files, MAX_PARALLEL_LIMIT, deleteFile, function(err) {
391+
if (err) {
392+
callback(err);
393+
return;
394+
}
395+
396+
if (nextQuery) {
397+
deleteFiles(nextQuery, callback);
398+
return;
399+
}
400+
401+
callback();
402+
});
403+
});
404+
}
405+
406+
function deleteFile(file, callback) {
407+
file.delete(function(err) {
408+
if (err) {
409+
if (query.force) {
410+
errors.push(err);
411+
callback();
412+
return;
413+
}
414+
415+
callback(err);
416+
return;
417+
}
418+
419+
callback();
420+
});
421+
}
422+
};
423+
313424
/**
314425
* Create a File object. See {module:storage/file} to see how to handle
315426
* the different use cases you may have.
@@ -849,7 +960,7 @@ Bucket.prototype.makeAllFilesPublicPrivate_ = function(options, callback) {
849960
var updatedFiles = [];
850961

851962
// Start processing files, iteratively fetching more as necessary.
852-
processFiles({}, function (err) {
963+
processFiles({}, function(err) {
853964
if (err || errors.length > 0) {
854965
callback(err || errors, updatedFiles);
855966
return;

system-test/storage.js

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -42,27 +42,6 @@ var files = {
4242
}
4343
};
4444

45-
function deleteVersionedFiles(bucket, callback) {
46-
bucket.getFiles({ versions: true }, function(err, files) {
47-
if (err) {
48-
callback(err);
49-
return;
50-
}
51-
52-
async.each(files, deleteFile, callback);
53-
});
54-
}
55-
56-
function deleteFiles(bucket, callback) {
57-
bucket.getFiles(function(err, files) {
58-
if (err) {
59-
callback(err);
60-
return;
61-
}
62-
async.map(files, deleteFile, callback);
63-
});
64-
}
65-
6645
function deleteFile(file, callback) {
6746
file.delete(callback);
6847
}
@@ -100,7 +79,7 @@ describe('storage', function() {
10079
});
10180

10281
after(function(done) {
103-
deleteFiles(bucket, function(err) {
82+
bucket.deleteFiles(function(err) {
10483
assert.ifError(err);
10584
bucket.delete(done);
10685
});
@@ -221,7 +200,7 @@ describe('storage', function() {
221200
bucket.acl.default.delete({ entity: 'allUsers' }, next);
222201
},
223202
function(next) {
224-
deleteFiles(bucket, next);
203+
bucket.deleteFiles(next);
225204
}
226205
], done);
227206
});
@@ -278,7 +257,7 @@ describe('storage', function() {
278257
async.each(files, isFilePrivate, function(err) {
279258
assert.ifError(err);
280259

281-
deleteFiles(bucket, done);
260+
bucket.deleteFiles(done);
282261
});
283262
});
284263
});
@@ -686,7 +665,7 @@ describe('storage', function() {
686665
var filenames = ['CloudLogo1', 'CloudLogo2', 'CloudLogo3'];
687666

688667
before(function(done) {
689-
deleteFiles(bucket, function(err) {
668+
bucket.deleteFiles(function(err) {
690669
assert.ifError(err);
691670

692671
var file = bucket.file(filenames[0]);
@@ -750,7 +729,7 @@ describe('storage', function() {
750729
});
751730

752731
afterEach(function(done) {
753-
deleteVersionedFiles(versionedBucket, done);
732+
versionedBucket.deleteFiles({ versions: true }, done);
754733
});
755734

756735
after(function(done) {

test/storage/bucket.js

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,124 @@ describe('Bucket', function() {
340340
});
341341
});
342342

343+
describe('deleteFiles', function() {
344+
it('should get files from the bucket', function(done) {
345+
var query = { a: 'b', c: 'd' };
346+
347+
bucket.getFiles = function(query_) {
348+
assert.deepEqual(query_, query);
349+
done();
350+
};
351+
352+
bucket.deleteFiles(query, assert.ifError);
353+
});
354+
355+
it('should process 10 files at a time', function(done) {
356+
eachLimit_Override = function(arr, limit) {
357+
assert.equal(limit, 10);
358+
done();
359+
};
360+
361+
bucket.getFiles = function(query, callback) {
362+
callback(null, []);
363+
};
364+
365+
bucket.deleteFiles({}, assert.ifError);
366+
});
367+
368+
it('should delete the files', function(done) {
369+
var timesCalled = 0;
370+
371+
var files = [
372+
bucket.file('1'),
373+
bucket.file('2')
374+
].map(util.propAssign('delete', function(callback) {
375+
timesCalled++;
376+
callback();
377+
}));
378+
379+
bucket.getFiles = function(query, callback) {
380+
callback(null, files);
381+
};
382+
383+
bucket.deleteFiles({}, function(err) {
384+
assert.ifError(err);
385+
assert.equal(timesCalled, files.length);
386+
done();
387+
});
388+
});
389+
390+
it('should get more files if more exist', function(done) {
391+
var fakeNextQuery = { a: 'b', c: 'd' };
392+
393+
bucket.getFiles = function(query, callback) {
394+
if (Object.keys(query).length === 0) {
395+
// First time through, return a `nextQuery` value.
396+
callback(null, [], fakeNextQuery);
397+
} else {
398+
// Second time through.
399+
assert.deepEqual(query, fakeNextQuery);
400+
done();
401+
}
402+
};
403+
404+
bucket.deleteFiles({}, assert.ifError);
405+
});
406+
407+
it('should execute callback with error from getting files', function(done) {
408+
var error = new Error('Error.');
409+
410+
bucket.getFiles = function(query, callback) {
411+
callback(error);
412+
};
413+
414+
bucket.deleteFiles({}, function(err) {
415+
assert.equal(err, error);
416+
done();
417+
});
418+
});
419+
420+
it('should execute callback with error from deleting file', function(done) {
421+
var error = new Error('Error.');
422+
423+
var files = [
424+
bucket.file('1'),
425+
bucket.file('2')
426+
].map(util.propAssign('delete', function(callback) {
427+
callback(error);
428+
}));
429+
430+
bucket.getFiles = function(query, callback) {
431+
callback(null, files);
432+
};
433+
434+
bucket.deleteFiles({}, function(err) {
435+
assert.equal(err, error);
436+
done();
437+
});
438+
});
439+
440+
it('should execute callback with queued errors', function(done) {
441+
var error = new Error('Error.');
442+
443+
var files = [
444+
bucket.file('1'),
445+
bucket.file('2')
446+
].map(util.propAssign('delete', function(callback) {
447+
callback(error);
448+
}));
449+
450+
bucket.getFiles = function(query, callback) {
451+
callback(null, files);
452+
};
453+
454+
bucket.deleteFiles({ force: true }, function(errs) {
455+
assert.deepEqual(errs, [error, error]);
456+
done();
457+
});
458+
});
459+
});
460+
343461
describe('file', function() {
344462
var FILE_NAME = 'remote-file-name.jpg';
345463
var file;

0 commit comments

Comments
 (0)