blob: 52e31b968f0e2ae0a17cb2e5468c04fff30cf16d [file] [log] [blame]
// Copyright 2020 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "starboard/loader_app/drain_file.h"
#include <algorithm>
#include <string>
#include <vector>
#include "starboard/common/file.h"
#include "starboard/common/log.h"
#include "starboard/configuration_constants.h"
#include "starboard/directory.h"
#include "starboard/string.h"
#ifdef __cplusplus
extern "C" {
#endif
const SbTime kDrainFileAgeUnit = kSbTimeSecond;
const SbTime kDrainFileMaximumAge = kSbTimeHour;
const char kDrainFilePrefix[] = "d_";
#ifdef __cplusplus
} // extern "C"
#endif
namespace starboard {
namespace loader_app {
namespace {
std::string ExtractAppKey(const std::string& str) {
const size_t begin = str.find_first_of('_') + 1;
const size_t end = str.find_last_of('_');
if ((begin == std::string::npos) || (end == std::string::npos) ||
(end - begin < 1))
return "";
return str.substr(begin, end - begin);
}
SbTime ExtractTimestamp(const std::string& str) {
const size_t index = str.find_last_of('_') + 1;
if ((index == std::string::npos) || (index == str.size() - 1))
return 0;
const std::string timestamp = str.substr(index, str.size() - index);
return SbTime(SbStringParseUInt64(timestamp.c_str(), NULL, 10)) *
kDrainFileAgeUnit;
}
bool IsExpired(const std::string& filename) {
const SbTime timestamp = ExtractTimestamp(filename);
return timestamp + kDrainFileMaximumAge < SbTimeGetNow();
}
std::vector<std::string> FindAllWithPrefix(const std::string& dir,
const std::string& prefix) {
SbDirectory slot = SbDirectoryOpen(dir.c_str(), NULL);
if (!SbDirectoryIsValid(slot)) {
SB_LOG(ERROR) << "Failed to open provided directory '" << dir << "'";
return std::vector<std::string>();
}
std::vector<std::string> filenames;
#if SB_API_VERSION >= 12
std::vector<char> filename(kSbFileMaxName);
while (SbDirectoryGetNext(slot, filename.data(), filename.size())) {
if (!SbStringCompareAll(filename.data(), ".") ||
!SbStringCompareAll(filename.data(), ".."))
continue;
if (!SbStringCompare(prefix.data(), filename.data(), prefix.size()))
filenames.push_back(std::string(filename.data()));
}
#else
SbDirectoryEntry entry;
while (SbDirectoryGetNext(slot, &entry)) {
if (!SbStringCompareAll(entry.name, ".") ||
!SbStringCompareAll(entry.name, ".."))
continue;
if (!SbStringCompare(prefix.data(), entry.name, prefix.size()))
filenames.push_back(std::string(entry.name));
}
#endif
SbDirectoryClose(slot);
return filenames;
}
void Rank(const char* dir, char* app_key, size_t len) {
SB_DCHECK(dir);
SB_DCHECK(app_key);
std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
std::remove_if(filenames.begin(), filenames.end(), IsExpired);
if (filenames.empty())
return;
// This lambda compares two strings, each string being a drain file name. This
// function returns |true| when |left| has an earlier timestamp than |right|,
// or when |left| is ASCII-betically lower than |right| if their timestamps
// are equal.
auto compare_filenames = [](const std::string& left,
const std::string& right) -> bool {
const SbTime left_timestamp = ExtractTimestamp(left);
const SbTime right_timestamp = ExtractTimestamp(right);
if (left_timestamp != right_timestamp)
return left_timestamp < right_timestamp;
const std::string left_app_key = ExtractAppKey(left);
const std::string right_app_key = ExtractAppKey(right);
return SbStringCompare(left_app_key.c_str(), right_app_key.c_str(),
right_app_key.size()) < 0;
};
std::sort(filenames.begin(), filenames.end(), compare_filenames);
const std::string& ranking_app_key = ExtractAppKey(filenames.front());
if (SbStringCopy(app_key, ranking_app_key.c_str(), len) >= len)
SB_LOG(ERROR) << "Returned value was truncated";
}
} // namespace
namespace drain_file {
bool TryDrain(const char* dir, const char* app_key) {
SB_DCHECK(dir);
SB_DCHECK(app_key);
std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
for (const auto& filename : filenames) {
if (IsExpired(filename))
continue;
if (filename.find(app_key) == std::string::npos)
return false;
SB_LOG(INFO) << "Found valid drain file '" << filename << "'";
return true;
}
std::string filename(kDrainFilePrefix);
filename.append(app_key);
filename.append("_");
filename.append(std::to_string(SbTimeGetNow() / kDrainFileAgeUnit));
SB_DCHECK(filename.size() <= kSbFileMaxName);
std::string path(dir);
path.append(kSbFileSepString);
path.append(filename);
SbFileError error = kSbFileOk;
SbFile file = SbFileOpen(path.c_str(), kSbFileCreateAlways | kSbFileWrite,
NULL, &error);
SB_DCHECK(error == kSbFileOk);
SB_DCHECK(SbFileClose(file));
SB_LOG(INFO) << "Created drain file at '" << path << "'";
return true;
}
bool RankAndCheck(const char* dir, const char* app_key) {
SB_DCHECK(dir);
SB_DCHECK(app_key);
std::vector<char> ranking_app_key(kSbFileMaxName);
Rank(dir, ranking_app_key.data(), ranking_app_key.size());
return !SbStringCompareAll(ranking_app_key.data(), app_key);
}
bool Remove(const char* dir, const char* app_key) {
SB_DCHECK(dir);
SB_DCHECK(app_key);
const std::string prefix = std::string(kDrainFilePrefix) + app_key;
const std::vector<std::string> filenames =
FindAllWithPrefix(dir, prefix.c_str());
for (const auto& filename : filenames) {
const std::string path = dir + std::string(kSbFileSepString) + filename;
if (!SbFileDelete(path.c_str()))
return false;
}
return true;
}
void Clear(const char* dir, const char* app_key, bool expired) {
SB_DCHECK(dir);
std::vector<std::string> filenames = FindAllWithPrefix(dir, kDrainFilePrefix);
for (const auto& filename : filenames) {
if (expired && !IsExpired(filename))
continue;
if (app_key && (filename.find(app_key) != std::string::npos))
continue;
const std::string path = dir + std::string(kSbFileSepString) + filename;
if (!SbFileDelete(path.c_str()))
SB_LOG(ERROR) << "Failed to remove expired drain file at '" << filename
<< "'";
}
}
void PrepareDirectory(const char* dir, const char* app_key) {
SB_DCHECK(dir);
SB_DCHECK(app_key);
const std::string prefix = std::string(kDrainFilePrefix) + app_key;
const std::vector<std::string> entries = FindAllWithPrefix(dir, "");
for (const auto& entry : entries) {
if (!SbStringCompare(entry.c_str(), prefix.c_str(), prefix.size()))
continue;
std::string path(dir);
path.append(kSbFileSepString);
path.append(entry);
SbFileDeleteRecursive(path.c_str(), false);
}
}
bool Draining(const char* dir, const char* app_key) {
SB_DCHECK(dir);
std::string prefix(kDrainFilePrefix);
if (app_key)
prefix.append(app_key);
const std::vector<std::string> filenames =
FindAllWithPrefix(dir, prefix.c_str());
for (const auto& filename : filenames) {
if (!IsExpired(filename))
return true;
}
return false;
}
} // namespace drain_file
} // namespace loader_app
} // namespace starboard
#ifdef __cplusplus
extern "C" {
#endif
bool DrainFileTryDrain(const char* dir, const char* app_key) {
return starboard::loader_app::drain_file::TryDrain(dir, app_key);
}
bool DrainFileRankAndCheck(const char* dir, const char* app_key) {
return starboard::loader_app::drain_file::RankAndCheck(dir, app_key);
}
bool DrainFileRemove(const char* dir, const char* app_key) {
return starboard::loader_app::drain_file::Remove(dir, app_key);
}
void DrainFileClear(const char* dir, const char* app_key, bool expired) {
starboard::loader_app::drain_file::Clear(dir, app_key, expired);
}
void DrainFilePrepareDirectory(const char* dir, const char* app_key) {
starboard::loader_app::drain_file::PrepareDirectory(dir, app_key);
}
bool DrainFileDraining(const char* dir, const char* app_key) {
return starboard::loader_app::drain_file::Draining(dir, app_key);
}
#ifdef __cplusplus
} // extern "C"
#endif