1/*
2 * Copyright (C) 2019 Apple Inc. All Rights Reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "StorageQuotaManager.h"
28
29#include "StorageQuotaUser.h"
30
31namespace WebCore {
32
33StorageQuotaManager::~StorageQuotaManager()
34{
35 while (!m_pendingRequests.isEmpty())
36 m_pendingRequests.takeFirst().callback(Decision::Deny);
37}
38
39uint64_t StorageQuotaManager::spaceUsage() const
40{
41 uint64_t usage = 0;
42 for (auto& user : m_users)
43 usage += user->spaceUsed();
44 return usage;
45}
46
47void StorageQuotaManager::updateQuotaBasedOnSpaceUsage()
48{
49 if (!m_quota)
50 return;
51
52 auto defaultQuotaStep = m_quota / 10;
53 m_quota = std::max(m_quota, defaultQuotaStep * ((spaceUsage() / defaultQuotaStep) + 1));
54}
55
56void StorageQuotaManager::initializeUsersIfNeeded()
57{
58 if (m_pendingInitializationUsers.isEmpty())
59 return;
60
61 Vector<StorageQuotaUser*> usersToInitialize;
62 for (auto& keyValue : m_pendingInitializationUsers) {
63 if (keyValue.value == WhenInitializedCalled::No) {
64 keyValue.value = WhenInitializedCalled::Yes;
65 usersToInitialize.append(keyValue.key);
66 }
67 }
68 for (auto* user : usersToInitialize) {
69 if (m_pendingInitializationUsers.contains(user))
70 askUserToInitialize(*user);
71 }
72}
73
74void StorageQuotaManager::askUserToInitialize(StorageQuotaUser& user)
75{
76 user.whenInitialized([this, &user, weakThis = makeWeakPtr(this)]() {
77 if (!weakThis)
78 return;
79
80 if (m_pendingInitializationUsers.remove(&user))
81 m_users.add(&user);
82
83 if (!m_pendingInitializationUsers.isEmpty())
84 return;
85
86 updateQuotaBasedOnSpaceUsage();
87 processPendingRequests({ }, ShouldDequeueFirstPendingRequest::No);
88 });
89}
90
91void StorageQuotaManager::addUser(StorageQuotaUser& user)
92{
93 ASSERT(!m_pendingInitializationUsers.contains(&user));
94 ASSERT(!m_users.contains(&user));
95 m_pendingInitializationUsers.add(&user, WhenInitializedCalled::No);
96
97 if (!m_pendingRequests.isEmpty())
98 askUserToInitialize(user);
99}
100
101bool StorageQuotaManager::shouldAskForMoreSpace(uint64_t spaceIncrease) const
102{
103 if (!spaceIncrease)
104 return false;
105
106 return spaceUsage() + spaceIncrease > m_quota;
107}
108
109void StorageQuotaManager::removeUser(StorageQuotaUser& user)
110{
111 ASSERT(m_users.contains(&user) || m_pendingInitializationUsers.contains(&user));
112 m_users.remove(&user);
113 if (m_pendingInitializationUsers.remove(&user) && m_pendingInitializationUsers.isEmpty()) {
114 // When being cleared, quota users may remove themselves and add themselves to trigger reinitialization.
115 // Let's wait for addUser to be called before processing pending requests.
116 callOnMainThread([this, weakThis = makeWeakPtr(this)] {
117 if (!weakThis)
118 return;
119
120 if (m_pendingInitializationUsers.isEmpty())
121 this->processPendingRequests({ }, ShouldDequeueFirstPendingRequest::No);
122 });
123 }
124}
125
126void StorageQuotaManager::requestSpace(uint64_t spaceIncrease, RequestCallback&& callback)
127{
128 if (!m_pendingRequests.isEmpty()) {
129 m_pendingRequests.append({ spaceIncrease, WTFMove(callback) });
130 return;
131 }
132
133 if (!spaceIncrease) {
134 callback(Decision::Grant);
135 return;
136 }
137
138 initializeUsersIfNeeded();
139
140 if (!m_pendingInitializationUsers.isEmpty()) {
141 m_pendingRequests.append({ spaceIncrease, WTFMove(callback) });
142 return;
143 }
144
145 if (shouldAskForMoreSpace(spaceIncrease)) {
146 m_pendingRequests.append({ spaceIncrease, WTFMove(callback) });
147 askForMoreSpace(spaceIncrease);
148 return;
149 }
150
151 callback(Decision::Grant);
152}
153
154void StorageQuotaManager::askForMoreSpace(uint64_t spaceIncrease)
155{
156 ASSERT(shouldAskForMoreSpace(spaceIncrease));
157 ASSERT(!m_isWaitingForSpaceIncreaseResponse);
158 m_isWaitingForSpaceIncreaseResponse = true;
159 m_spaceIncreaseRequester(m_quota, spaceUsage(), spaceIncrease, [this, weakThis = makeWeakPtr(*this)](Optional<uint64_t> newQuota) {
160 if (!weakThis)
161 return;
162 m_isWaitingForSpaceIncreaseResponse = false;
163 processPendingRequests(newQuota, ShouldDequeueFirstPendingRequest::Yes);
164 });
165}
166
167void StorageQuotaManager::processPendingRequests(Optional<uint64_t> newQuota, ShouldDequeueFirstPendingRequest shouldDequeueFirstPendingRequest)
168{
169 if (m_pendingRequests.isEmpty())
170 return;
171
172 if (newQuota)
173 m_quota = *newQuota;
174
175 if (m_isWaitingForSpaceIncreaseResponse)
176 return;
177
178 if (!m_pendingInitializationUsers.isEmpty())
179 return;
180
181 if (shouldDequeueFirstPendingRequest == ShouldDequeueFirstPendingRequest::Yes) {
182 auto request = m_pendingRequests.takeFirst();
183 auto decision = shouldAskForMoreSpace(request.spaceIncrease) ? Decision::Deny : Decision::Grant;
184 request.callback(decision);
185 }
186
187 while (!m_pendingRequests.isEmpty()) {
188 auto& request = m_pendingRequests.first();
189
190 if (shouldAskForMoreSpace(request.spaceIncrease)) {
191 uint64_t spaceIncrease = 0;
192 for (auto& request : m_pendingRequests)
193 spaceIncrease += request.spaceIncrease;
194 askForMoreSpace(spaceIncrease);
195 return;
196 }
197
198 m_pendingRequests.takeFirst().callback(Decision::Grant);
199 }
200}
201
202} // namespace WebCore
203