Skip to content

Commit 7602426

Browse files
Allow deletion of empty folders
(even though we normally don't allow operations on folders since they are not represented). This helps with NIO-using code that must also work with filesystems that *do* represent folders.
1 parent 68ce84d commit 7602426

3 files changed

Lines changed: 81 additions & 2 deletions

File tree

google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProvider.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import java.nio.file.DirectoryStream.Filter;
4949
import java.nio.file.FileAlreadyExistsException;
5050
import java.nio.file.FileStore;
51+
import java.nio.file.Files;
5152
import java.nio.file.LinkOption;
5253
import java.nio.file.NoSuchFileException;
5354
import java.nio.file.OpenOption;
@@ -351,6 +352,15 @@ public boolean deleteIfExists(Path path) throws IOException {
351352
initStorage();
352353
CloudStoragePath cloudPath = CloudStorageUtil.checkPath(path);
353354
if (cloudPath.seemsLikeADirectoryAndUsePseudoDirectories()) {
355+
// if the "folder" is empty then we're fine, otherwise complain
356+
// that we cannot act on folders.
357+
try (DirectoryStream<Path> paths = Files.newDirectoryStream(path)) {
358+
if (!paths.iterator().hasNext()) {
359+
// "folder" isn't actually there in the first place, so: success!
360+
// (we must return true so delete doesn't freak out)
361+
return true;
362+
}
363+
}
354364
throw new CloudStoragePseudoDirectoryException(cloudPath);
355365
}
356366
return storage.delete(cloudPath.getBlobId());

google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemProviderTest.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,6 @@ public void testDelete_dotDirNotNormalized_throwsIae() throws IOException {
413413

414414
@Test
415415
public void testDelete_trailingSlash() throws IOException {
416-
thrown.expect(CloudStoragePseudoDirectoryException.class);
417416
Files.delete(Paths.get(URI.create("gs://love/passion/")));
418417
}
419418

@@ -442,7 +441,6 @@ public void testDeleteIfExists() throws IOException {
442441

443442
@Test
444443
public void testDeleteIfExists_trailingSlash() throws IOException {
445-
thrown.expect(CloudStoragePseudoDirectoryException.class);
446444
Files.deleteIfExists(Paths.get(URI.create("gs://love/passion/")));
447445
}
448446

google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/CloudStorageFileSystemTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,77 @@ public void testMatcher() throws IOException {
180180
}
181181
}
182182

183+
@Test
184+
public void testDeleteEmptyFolder() throws IOException {
185+
try (FileSystem fs = CloudStorageFileSystem.forBucket("bucket")) {
186+
List<Path> paths = new ArrayList<>();
187+
paths.add(fs.getPath("dir/angel"));
188+
paths.add(fs.getPath("dir/dir2/another_angel"));
189+
paths.add(fs.getPath("atroot"));
190+
for (Path path : paths) {
191+
Files.write(path, ALONE.getBytes(UTF_8));
192+
}
193+
// we can delete non-existent folders, because they are not represented on disk anyways.
194+
Files.delete(fs.getPath("ghost/"));
195+
Files.delete(fs.getPath("dir/ghost/"));
196+
Files.delete(fs.getPath("dir/dir2/ghost/"));
197+
// likewise, deleteIfExists works.
198+
Files.deleteIfExists(fs.getPath("ghost/"));
199+
Files.deleteIfExists(fs.getPath("dir/ghost/"));
200+
Files.deleteIfExists(fs.getPath("dir/dir2/ghost/"));
201+
}
202+
}
203+
204+
@Test(expected = CloudStoragePseudoDirectoryException.class)
205+
public void testDeleteFullFolder() throws IOException {
206+
try (FileSystem fs = CloudStorageFileSystem.forBucket("bucket")) {
207+
Files.write(fs.getPath("dir/angel"), ALONE.getBytes(UTF_8));
208+
// we cannot delete existing folders if they contain something
209+
Files.delete(fs.getPath("dir/"));
210+
}
211+
}
212+
213+
@Test
214+
public void testDelete() throws IOException {
215+
try (FileSystem fs = CloudStorageFileSystem.forBucket("bucket")) {
216+
List<Path> paths = new ArrayList<>();
217+
paths.add(fs.getPath("dir/angel"));
218+
paths.add(fs.getPath("dir/dir2/another_angel"));
219+
paths.add(fs.getPath("atroot"));
220+
for (Path path : paths) {
221+
Files.write(path, ALONE.getBytes(UTF_8));
222+
}
223+
Files.delete(fs.getPath("atroot"));
224+
Files.delete(fs.getPath("dir/angel"));
225+
Files.deleteIfExists(fs.getPath("dir/dir2/another_angel"));
226+
227+
for (Path path : paths) {
228+
assertThat(Files.exists(path)).isFalse();
229+
}
230+
}
231+
}
232+
233+
@Test
234+
public void testDeleteEmptiedFolder() throws IOException {
235+
try (FileSystem fs = CloudStorageFileSystem.forBucket("bucket")) {
236+
List<Path> paths = new ArrayList<>();
237+
paths.add(fs.getPath("dir/angel"));
238+
paths.add(fs.getPath("dir/dir2/another_angel"));
239+
for (Path path : paths) {
240+
Files.write(path, ALONE.getBytes(UTF_8));
241+
}
242+
Files.delete(fs.getPath("dir/angel"));
243+
Files.deleteIfExists(fs.getPath("dir/dir2/another_angel"));
244+
// delete folder (trailing slash is required)
245+
Path dir2 = fs.getPath("dir/dir2/");
246+
Files.deleteIfExists(dir2);
247+
// We can't check Files.exists on a folder (since GCS fakes folders),
248+
Path dir = fs.getPath("dir/");
249+
Files.deleteIfExists(dir);
250+
}
251+
}
252+
253+
183254
private void assertMatches(FileSystem fs, PathMatcher matcher, String toMatch, boolean expected) {
184255
assertThat(matcher.matches(fs.getPath(toMatch).getFileName())).isEqualTo(expected);
185256
}

0 commit comments

Comments
 (0)