| /* |
| * Copyright (C) 2019 Apple Inc. All Rights Reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "StorageQuotaManager.h" |
| |
| #include "Logging.h" |
| #include "StorageQuotaUser.h" |
| |
| namespace WebCore { |
| |
| StorageQuotaManager::~StorageQuotaManager() |
| { |
| while (!m_pendingRequests.isEmpty()) |
| m_pendingRequests.takeFirst().callback(Decision::Deny); |
| } |
| |
| uint64_t StorageQuotaManager::spaceUsage() const |
| { |
| uint64_t usage = 0; |
| for (auto& user : m_users) |
| usage += user->spaceUsed(); |
| return usage; |
| } |
| |
| void StorageQuotaManager::updateQuotaBasedOnSpaceUsage() |
| { |
| if (!m_quota) |
| return; |
| |
| auto defaultQuotaStep = m_quota / 10; |
| m_quota = std::max(m_quota, defaultQuotaStep * ((spaceUsage() / defaultQuotaStep) + 1)); |
| } |
| |
| void StorageQuotaManager::initializeUsersIfNeeded() |
| { |
| if (m_pendingInitializationUsers.isEmpty()) |
| return; |
| |
| Vector<StorageQuotaUser*> usersToInitialize; |
| for (auto& keyValue : m_pendingInitializationUsers) { |
| if (keyValue.value == WhenInitializedCalled::No) { |
| keyValue.value = WhenInitializedCalled::Yes; |
| usersToInitialize.append(keyValue.key); |
| } |
| } |
| for (auto* user : usersToInitialize) { |
| if (m_pendingInitializationUsers.contains(user)) |
| askUserToInitialize(*user); |
| } |
| } |
| |
| void StorageQuotaManager::askUserToInitialize(StorageQuotaUser& user) |
| { |
| user.whenInitialized([this, &user, weakThis = makeWeakPtr(this)]() { |
| if (!weakThis) |
| return; |
| |
| if (m_pendingInitializationUsers.remove(&user)) |
| m_users.add(&user); |
| |
| if (!m_pendingInitializationUsers.isEmpty()) |
| return; |
| |
| // Make sure quota is set before handling first request. |
| if (m_state == State::Uninitialized) { |
| updateQuotaBasedOnSpaceUsage(); |
| m_state = State::MakingDecisionForRequest; |
| } |
| |
| processPendingRequests({ }); |
| }); |
| } |
| |
| void StorageQuotaManager::addUser(StorageQuotaUser& user) |
| { |
| ASSERT(!m_pendingInitializationUsers.contains(&user)); |
| ASSERT(!m_users.contains(&user)); |
| m_pendingInitializationUsers.add(&user, WhenInitializedCalled::No); |
| |
| if (!m_pendingRequests.isEmpty()) |
| askUserToInitialize(user); |
| } |
| |
| bool StorageQuotaManager::shouldAskForMoreSpace(uint64_t spaceIncrease) const |
| { |
| if (!spaceIncrease) |
| return false; |
| |
| return spaceUsage() + spaceIncrease > m_quota; |
| } |
| |
| void StorageQuotaManager::removeUser(StorageQuotaUser& user) |
| { |
| ASSERT(m_users.contains(&user) || m_pendingInitializationUsers.contains(&user)); |
| m_users.remove(&user); |
| if (m_pendingInitializationUsers.remove(&user) && m_pendingInitializationUsers.isEmpty()) { |
| // When being cleared, quota users may remove themselves and add themselves to trigger reinitialization. |
| // Let's wait for addUser to be called before processing pending requests. |
| callOnMainThread([this, weakThis = makeWeakPtr(this)] { |
| if (!weakThis) |
| return; |
| |
| if (m_pendingInitializationUsers.isEmpty()) |
| this->processPendingRequests({ }); |
| }); |
| } |
| } |
| |
| void StorageQuotaManager::requestSpace(uint64_t spaceIncrease, RequestCallback&& callback) |
| { |
| if (!m_pendingRequests.isEmpty()) { |
| m_pendingRequests.append({ spaceIncrease, WTFMove(callback) }); |
| return; |
| } |
| |
| if (!spaceIncrease) { |
| callback(Decision::Grant); |
| return; |
| } |
| |
| initializeUsersIfNeeded(); |
| |
| if (!m_pendingInitializationUsers.isEmpty()) { |
| m_pendingRequests.append({ spaceIncrease, WTFMove(callback) }); |
| return; |
| } |
| |
| if (shouldAskForMoreSpace(spaceIncrease)) { |
| m_pendingRequests.append({ spaceIncrease, WTFMove(callback) }); |
| |
| // Try processing request again after making sure usage is accurate. |
| m_state = State::ComputingSpaceUsed; |
| for (auto& user : copyToVector(m_users)) |
| user->computeSpaceUsed(); |
| |
| if (!m_pendingInitializationUsers.isEmpty()) |
| return; |
| |
| m_state = State::AskingForMoreSpace; |
| askForMoreSpace(spaceIncrease); |
| return; |
| } |
| |
| m_state = State::MakingDecisionForRequest; |
| callback(Decision::Grant); |
| } |
| |
| void StorageQuotaManager::askForMoreSpace(uint64_t spaceIncrease) |
| { |
| ASSERT(shouldAskForMoreSpace(spaceIncrease)); |
| ASSERT(m_state == State::AskingForMoreSpace); |
| |
| RELEASE_LOG(Storage, "%p - StorageQuotaManager::askForMoreSpace %" PRIu64, this, spaceIncrease); |
| m_state = State::WaitingForSpaceIncreaseResponse; |
| m_spaceIncreaseRequester(m_quota, spaceUsage(), spaceIncrease, [this, weakThis = makeWeakPtr(*this)](Optional<uint64_t> newQuota) { |
| if (!weakThis) |
| return; |
| |
| RELEASE_LOG(Storage, "%p - StorageQuotaManager::askForMoreSpace received response %" PRIu64, this, newQuota ? *newQuota : 0); |
| |
| m_state = State::AskingForMoreSpace; |
| processPendingRequests(newQuota); |
| }); |
| } |
| |
| void StorageQuotaManager::processPendingRequests(Optional<uint64_t> newQuota) |
| { |
| if (m_pendingRequests.isEmpty()) |
| return; |
| |
| if (newQuota) |
| m_quota = *newQuota; |
| |
| if (m_state == State::WaitingForSpaceIncreaseResponse) |
| return; |
| |
| if (!m_pendingInitializationUsers.isEmpty()) |
| return; |
| |
| if (m_state == State::AskingForMoreSpace) { |
| auto request = m_pendingRequests.takeFirst(); |
| bool shouldAllowRequest = !shouldAskForMoreSpace(request.spaceIncrease); |
| |
| RELEASE_LOG(Storage, "%p - StorageQuotaManager::processPendingRequests first request decision is %d", this, shouldAllowRequest); |
| |
| m_state = State::MakingDecisionForRequest; |
| request.callback(shouldAllowRequest ? Decision::Grant : Decision::Deny); |
| } |
| |
| while (!m_pendingRequests.isEmpty()) { |
| auto& request = m_pendingRequests.first(); |
| |
| if (shouldAskForMoreSpace(request.spaceIncrease)) { |
| if (m_state == State::MakingDecisionForRequest) { |
| m_state = State::ComputingSpaceUsed; |
| for (auto& user : copyToVector(m_users)) |
| user->computeSpaceUsed(); |
| |
| if (!m_pendingInitializationUsers.isEmpty()) |
| return; |
| } |
| |
| m_state = State::AskingForMoreSpace; |
| uint64_t spaceIncrease = 0; |
| for (auto& pendingRequest : m_pendingRequests) |
| spaceIncrease += pendingRequest.spaceIncrease; |
| askForMoreSpace(spaceIncrease); |
| return; |
| } |
| |
| m_state = State::MakingDecisionForRequest; |
| m_pendingRequests.takeFirst().callback(Decision::Grant); |
| } |
| } |
| |
| } // namespace WebCore |