return Status::OK();
}
}
- bool result;
- TF_RETURN_IF_ERROR(ObjectExists(fname, bucket, object, &result));
- if (result) {
- return Status::OK();
+
+ // Check if the object exists.
+ GcsFileStat stat;
+ const Status status = StatForObject(fname, bucket, object, &stat);
+ if (status.code() != errors::Code::NOT_FOUND) {
+ return status;
}
+
+ // Check if the folder exists.
+ bool result;
TF_RETURN_IF_ERROR(FolderExists(fname, &result));
if (result) {
return Status::OK();
if (!result) {
return errors::Internal("'result' cannot be nullptr.");
}
- GcsFileStat not_used_stat;
- const Status status = StatForObject(fname, bucket, object, ¬_used_stat);
+ GcsFileStat stat;
+ const Status status = StatForObject(fname, bucket, object, &stat);
switch (status.code()) {
case errors::Code::OK:
- *result = true;
+ *result = !stat.base.is_directory;
return Status::OK();
case errors::Code::NOT_FOUND:
*result = false;
<< "; mtime_nsec: " << stat->base.mtime_nsec
<< "; updated: " << updated;
- stat->base.is_directory = false;
+ if (str_util::EndsWith(fname, "/")) {
+ // In GCS a path can be both a directory and a file, both it is uncommon for
+ // other file systems. To avoid the ambiguity, if a path ends with "/" in
+ // GCS, we always regard it as a directory mark or a virtual directory.
+ stat->base.is_directory = true;
+ } else {
+ stat->base.is_directory = false;
+ }
return Status::OK();
}
[this, &bucket, &object](const string& fname, GcsFileStat* stat) {
return UncachedStatForObject(fname, bucket, object, stat);
}));
- if (stat->base.is_directory) {
- return errors::NotFound(fname, " is a directory.");
- } else {
- return Status::OK();
- }
+ return Status::OK();
}
Status GcsFileSystem::BucketExists(const string& bucket, bool* result) {
}
}
+TEST(GcsFileSystemTest, FileExists_DirectoryMark) {
+ std::vector<HttpRequest*> requests({new FakeHttpRequest(
+ "Uri: https://www.googleapis.com/storage/v1/b/bucket/o/"
+ "dir%2F?fields=size%2Cgeneration%2Cupdated\n"
+ "Auth Token: fake_token\n"
+ "Timeouts: 5 1 10\n",
+ strings::StrCat("{\"size\": \"5\",\"generation\": \"1\","
+ "\"updated\": \"2016-04-29T23:15:24.896Z\"}"))});
+ GcsFileSystem fs(
+ std::unique_ptr<AuthProvider>(new FakeAuthProvider),
+ std::unique_ptr<HttpRequest::Factory>(
+ new FakeHttpRequestFactory(&requests)),
+ 0 /* block size */, 0 /* max bytes */, 0 /* max staleness */,
+ 3600 /* stat cache max age */, 0 /* stat cache max entries */,
+ 0 /* matching paths cache max age */,
+ 0 /* matching paths cache max entries */, 0 /* initial retry delay */,
+ kTestTimeoutConfig, nullptr /* gcs additional header */);
+
+ TF_EXPECT_OK(fs.FileExists("gs://bucket/dir/"));
+ TF_EXPECT_OK(fs.IsDirectory("gs://bucket/dir/"));
+}
+
TEST(GcsFileSystemTest, GetChildren_NoItems) {
std::vector<HttpRequest*> requests({new FakeHttpRequest(
"Uri: https://www.googleapis.com/storage/v1/b/bucket/o?"
}
}
+TEST(GcsFileSystemTest, Stat_FilenameEndingWithSlash) {
+ std::vector<HttpRequest*> requests({new FakeHttpRequest(
+ "Uri: https://www.googleapis.com/storage/v1/b/bucket/o/"
+ "dir%2F?fields=size%2Cgeneration%2Cupdated\n"
+ "Auth Token: fake_token\n"
+ "Timeouts: 5 1 10\n",
+ strings::StrCat("{\"size\": \"5\",\"generation\": \"1\","
+ "\"updated\": \"2016-04-29T23:15:24.896Z\"}"))});
+ GcsFileSystem fs(std::unique_ptr<AuthProvider>(new FakeAuthProvider),
+ std::unique_ptr<HttpRequest::Factory>(
+ new FakeHttpRequestFactory(&requests)),
+ 0 /* block size */, 0 /* max bytes */, 0 /* max staleness */,
+ 0 /* stat cache max age */, 0 /* stat cache max entries */,
+ 0 /* matching paths cache max age */,
+ 0 /* matching paths cache max entries */,
+ 0 /* initial retry delay*/, kTestTimeoutConfig,
+ nullptr /* gcs additional header */);
+
+ FileStatistics stat;
+ TF_EXPECT_OK(fs.Stat("gs://bucket/dir/", &stat));
+ EXPECT_EQ(5, stat.length);
+ EXPECT_TRUE(stat.is_directory);
+}
+
TEST(GcsFileSystemTest, IsDirectory_NotFound) {
std::vector<HttpRequest*> requests(
{new FakeHttpRequest(