Skip to content

Commit 72b1c2e

Browse files
committed
chore: add fs API unlinkEmptyDirectoryAsync and tests
1 parent 30476cb commit 72b1c2e

2 files changed

Lines changed: 123 additions & 15 deletions

File tree

src/filesystem/Directory.js

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -121,41 +121,70 @@ define(function (require, exports, module) {
121121
}
122122

123123
/**
124-
* Read the contents of a Directory, returns a promise. If this Directory is under a watch root,
125-
* the listing will exclude any items filtered out by the watch root's filter
126-
* function.
124+
* Returns true if is a directory exists and is empty.
127125
*
128-
* @return {Promise<{entries: FileSystemEntry, contentStats: FileSystemStats, contentsStatsErrors}>} An object
129-
* with attributes - entries(an array of file system entries), contentStats and contentsStatsErrors(a map from
130-
* content name to error if there is any).
126+
* @return {Promise<boolean>} True if directory is empty and it exists, else false.
131127
*/
132-
Directory.prototype.getContentsAsync = function () {
128+
Directory.prototype.isEmptyAsync = function () {
133129
let that = this;
134130
return new Promise((resolve, reject)=>{
135-
that.getContents((err, contents, contentStats, contentsStatsErrors) =>{
131+
that.getContents((err, contents) =>{
136132
if(err){
137133
reject(err);
138134
return;
139135
}
140-
resolve({entries: contents, contentStats, contentsStatsErrors});
136+
resolve(contents.length === 0);
141137
});
142138
});
143139
};
144140

145141
/**
146-
* Returns true if is a directory exists and is empty.
142+
* Recursively deletes all empty subdirectories within the current directory. If all subdirectories are empty,
143+
* the current directory itself will be deleted.
144+
* A directory is considered empty if it doesn't contain any files in its subtree.
147145
*
148-
* @return {Promise<boolean>} True if directory is empty and it exists, else false.
146+
* If a subtree contains a large number of nested subdirectories and no files, the whole tree will be deleted.
147+
* Only branches that contain a file will be retained.
148+
*
149+
* @returns {Promise<void>} A Promise that resolves when the operation is finished
150+
* @throws {FileSystemError} If an error occurs while accessing the filesystem
151+
*
152+
* @example
153+
*
154+
* await dir.unlinkEmptyDirectoryAsync();
149155
*/
150-
Directory.prototype.isEmptyAsync = function () {
156+
Directory.prototype.unlinkEmptyDirectoryAsync = async function () {
157+
let that = this;
158+
let {entries} = await that.getContentsAsync();
159+
for(let entry of entries){
160+
if(entry.isDirectory) {
161+
await entry.unlinkEmptyDirectoryAsync();
162+
}
163+
}
164+
let isEmpty = await that.isEmptyAsync();
165+
if(isEmpty){
166+
await that.unlinkAsync();
167+
}
168+
};
169+
170+
/**
171+
* Read the contents of a Directory, returns a promise. If this Directory is under a watch root,
172+
* the listing will exclude any items filtered out by the watch root's filter
173+
* function.
174+
*
175+
* @return {Promise<{entries: FileSystemEntry, contentStats: FileSystemStats, contentsStatsErrors}>} An object
176+
* with attributes - entries(an array of file system entries), contentStats and contentsStatsErrors(a map from
177+
* content name to error if there is any).
178+
*/
179+
Directory.prototype.getContentsAsync = function () {
151180
let that = this;
152181
return new Promise((resolve, reject)=>{
153-
that.getContents((err, contents) =>{
182+
that.getContents((err, contents, contentStats, contentsStatsErrors) =>{
154183
if(err){
155184
reject(err);
156185
return;
157186
}
158-
resolve(contents.length === 0);
187+
resolve({entries: contents, contentStats, contentsStatsErrors});
159188
});
160189
});
161190
};

test/spec/FileSystem-test.js

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@
1919
*
2020
*/
2121

22-
/*global describe, it, expect, beforeEach, afterEach, awaits, awaitsFor, awaitsForDone, jasmine, spyOn */
22+
/*global describe, it, expect, beforeEach, afterEach, awaits, awaitsFor, awaitsForDone, jasmine, spyOn, jsPromise */
2323

2424
define(function (require, exports, module) {
2525

2626

2727
var Directory = require("filesystem/Directory"),
2828
File = require("filesystem/File"),
2929
FileSystem = require("filesystem/FileSystem"),
30+
FileUtils = require("file/FileUtils"),
3031
FileSystemStats = require("filesystem/FileSystemStats"),
3132
FileSystemError = require("filesystem/FileSystemError"),
3233
MockFileSystemImpl = require("./MockFileSystemImpl"),
@@ -569,6 +570,84 @@ define(function (require, exports, module) {
569570
});
570571
});
571572

573+
describe("unlinkEmptyDirectoryAsync tests", function () {
574+
it("should unlinkEmptyDirectoryAsync delete self if empty", async function () {
575+
let directory = fileSystem.getDirectoryForPath("/subdir2/");
576+
await directory.createAsync();
577+
let stat = await directory.statAsync();
578+
expect(stat.isDirectory).toBeTrue();
579+
580+
await directory.unlinkEmptyDirectoryAsync();
581+
let isExists = await directory.existsAsync();
582+
expect(isExists).toBeFalse();
583+
});
584+
585+
it("should unlinkEmptyDirectoryAsync reject if directory doesnt exist", async function () {
586+
let directory = fileSystem.getDirectoryForPath("/subdir2/");
587+
let isExists = await directory.existsAsync();
588+
expect(isExists).toBeFalse();
589+
590+
let err;
591+
try{
592+
await directory.unlinkEmptyDirectoryAsync();
593+
} catch (e) {
594+
err = e;
595+
}
596+
expect(err).toBe(FileSystemError.NOT_FOUND);
597+
});
598+
599+
async function createDirTree(dirList, fileList) {
600+
for(let dir of dirList){
601+
let directory = fileSystem.getDirectoryForPath(dir);
602+
await directory.createAsync();
603+
}
604+
for(let file of fileList){
605+
let fileEntry = fileSystem.getFileForPath(file);
606+
await jsPromise(FileUtils.writeText(fileEntry, "hello", true));
607+
}
608+
}
609+
async function verifyExists(deletedPaths, shouldExist, isFile = false) {
610+
for(let deletedPath of deletedPaths){
611+
let entry = isFile ? fileSystem.getFileForPath(deletedPath)
612+
:fileSystem.getDirectoryForPath(deletedPath);
613+
let isExists = await entry.existsAsync();
614+
expect(deletedPath + ":" + isExists).toBe(deletedPath + ":" + shouldExist);
615+
}
616+
}
617+
it("should unlinkEmptyDirectoryAsync delete subtree with no files", async function () {
618+
await createDirTree(["/subdir2/", "/subdir2/s3/", "/subdir2/s4/", "/subdir2/s3/s33/"], []);
619+
620+
let directory = fileSystem.getDirectoryForPath("/subdir2/");
621+
await directory.unlinkEmptyDirectoryAsync();
622+
let isExists = await directory.existsAsync();
623+
expect(isExists).toBeFalse();
624+
});
625+
626+
it("should unlinkEmptyDirectoryAsync retain subtree with files", async function () {
627+
await createDirTree(["/subdir2/", "/subdir2/s3/", "/subdir2/s4/", "/subdir2/s3/s33/"],
628+
["/subdir2/1.txt"]);
629+
630+
let directory = fileSystem.getDirectoryForPath("/subdir2/");
631+
await directory.unlinkEmptyDirectoryAsync();
632+
633+
await verifyExists(["/subdir2/s3/", "/subdir2/s4/", "/subdir2/s3/s33/"], false);
634+
await verifyExists(["/subdir2/"], true);
635+
await verifyExists(["/subdir2/1.txt"], true, true);
636+
});
637+
638+
it("should unlinkEmptyDirectoryAsync retain subtree with files 2", async function () {
639+
await createDirTree(["/subdir2/", "/subdir2/s3/", "/subdir2/s3/s33/", "/subdir2/s3/s33/s333/"],
640+
["/subdir2/s3/s33/1.txt"]);
641+
642+
let directory = fileSystem.getDirectoryForPath("/subdir2/s3/");
643+
await directory.unlinkEmptyDirectoryAsync();
644+
645+
await verifyExists(["/subdir2/s3/s33/s333/"], false);
646+
await verifyExists(["/subdir2/", "/subdir2/s3/", "/subdir2/s3/s33/"], true);
647+
await verifyExists(["/subdir2/s3/s33/1.txt"], true, true);
648+
});
649+
});
650+
572651
describe("Get Free Path", function () {
573652
it("should return suggested path as is if it doesnt exist", async function () {
574653
let cbCalled = false,

0 commit comments

Comments
 (0)