15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 51320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "storage/browser/fileapi/file_system_quota_client.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <algorithm> 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include <set> 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/bind.h" 112a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles)#include "base/files/file_path.h" 121320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "base/files/file_util.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/location.h" 145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h" 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/memory/scoped_ptr.h" 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/sequenced_task_runner.h" 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/single_thread_task_runner.h" 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/task_runner_util.h" 195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/base/net_util.h" 201320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "storage/browser/fileapi/file_system_context.h" 211320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "storage/browser/fileapi/file_system_quota_util.h" 221320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "storage/browser/fileapi/file_system_usage_cache.h" 231320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "storage/browser/fileapi/sandbox_file_system_backend.h" 241320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci#include "storage/common/fileapi/file_system_util.h" 25eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "url/gurl.h" 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)using storage::StorageType; 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)namespace storage { 305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace { 325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 335d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void GetOriginsForTypeOnFileTaskRunner( 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemContext* context, 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StorageType storage_type, 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::set<GURL>* origins_ptr) { 375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); 385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(type != kFileSystemTypeUnknown); 395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemQuotaUtil* quota_util = context->GetQuotaUtil(type); 415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!quota_util) 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 435d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) quota_util->GetOriginsForTypeOnFileTaskRunner(type, origins_ptr); 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 465d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)void GetOriginsForHostOnFileTaskRunner( 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemContext* context, 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StorageType storage_type, 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& host, 505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::set<GURL>* origins_ptr) { 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(type != kFileSystemTypeUnknown); 535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemQuotaUtil* quota_util = context->GetQuotaUtil(type); 555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!quota_util) 565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) quota_util->GetOriginsForHostOnFileTaskRunner(type, host, origins_ptr); 585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)void DidGetOrigins(const storage::QuotaClient::GetOriginsCallback& callback, 6103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) std::set<GURL>* origins_ptr) { 6290dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) callback.Run(*origins_ptr); 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 6503b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)storage::QuotaStatusCode DeleteOriginOnFileTaskRunner( 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemContext* context, 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GURL& origin, 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemType type) { 697dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch FileSystemBackend* provider = context->GetFileSystemBackend(type); 70eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (!provider || !provider->GetQuotaUtil()) 7103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return storage::kQuotaErrorNotSupported; 725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::File::Error result = 735d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) provider->GetQuotaUtil()->DeleteOriginDataOnFileTaskRunner( 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) context, context->quota_manager_proxy(), origin, type); 755d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (result == base::File::FILE_OK) 7603b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return storage::kQuotaStatusOk; 7703b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return storage::kQuotaErrorInvalidModification; 785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)FileSystemQuotaClient::FileSystemQuotaClient( 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemContext* file_system_context, 845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bool is_incognito) 855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) : file_system_context_(file_system_context), 865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) is_incognito_(is_incognito) { 875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)FileSystemQuotaClient::~FileSystemQuotaClient() {} 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 9103b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)storage::QuotaClient::ID FileSystemQuotaClient::id() const { 9203b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) return storage::QuotaClient::kFileSystem; 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FileSystemQuotaClient::OnQuotaManagerDestroyed() { 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delete this; 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FileSystemQuotaClient::GetOriginUsage( 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GURL& origin_url, 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StorageType storage_type, 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GetUsageCallback& callback) { 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(!callback.is_null()); 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (is_incognito_) { 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We don't support FileSystem in incognito mode yet. 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) callback.Run(0); 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(type != kFileSystemTypeUnknown); 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemQuotaUtil* quota_util = file_system_context_->GetQuotaUtil(type); 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!quota_util) { 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) callback.Run(0); 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::PostTaskAndReplyWithResult( 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_task_runner(), 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FROM_HERE, 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // It is safe to pass Unretained(quota_util) since context owns it. 1245d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::Bind(&FileSystemQuotaUtil::GetOriginUsageOnFileTaskRunner, 1255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::Unretained(quota_util), 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_system_context_, 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) origin_url, 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) type), 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) callback); 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FileSystemQuotaClient::GetOriginsForType( 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StorageType storage_type, 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GetOriginsCallback& callback) { 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(!callback.is_null()); 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (is_incognito_) { 1385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We don't support FileSystem in incognito mode yet. 1395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::set<GURL> origins; 14090dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) callback.Run(origins); 1415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::set<GURL>* origins_ptr = new std::set<GURL>(); 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_task_runner()->PostTaskAndReply( 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FROM_HERE, 1475d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::Bind(&GetOriginsForTypeOnFileTaskRunner, 1485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_system_context_, 1495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) storage_type, 1505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::Unretained(origins_ptr)), 1515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::Bind(&DidGetOrigins, 1525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) callback, 15390dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) base::Owned(origins_ptr))); 1545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FileSystemQuotaClient::GetOriginsForHost( 1575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StorageType storage_type, 1585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& host, 1595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GetOriginsCallback& callback) { 1605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(!callback.is_null()); 1615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (is_incognito_) { 1635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We don't support FileSystem in incognito mode yet. 1645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::set<GURL> origins; 16590dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) callback.Run(origins); 1665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 1675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 1685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::set<GURL>* origins_ptr = new std::set<GURL>(); 1705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_task_runner()->PostTaskAndReply( 1715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FROM_HERE, 1725d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::Bind(&GetOriginsForHostOnFileTaskRunner, 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_system_context_, 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) storage_type, 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) host, 1765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::Unretained(origins_ptr)), 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::Bind(&DidGetOrigins, 1785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) callback, 17990dce4d38c5ff5333bea97d859d4e484e27edf0cTorne (Richard Coles) base::Owned(origins_ptr))); 1805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void FileSystemQuotaClient::DeleteOriginData( 1835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const GURL& origin, 1845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) StorageType type, 1855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const DeletionCallback& callback) { 1865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FileSystemType fs_type = QuotaStorageTypeToFileSystemType(type); 1875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(fs_type != kFileSystemTypeUnknown); 1885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) base::PostTaskAndReplyWithResult( 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_task_runner(), 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) FROM_HERE, 1925d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) base::Bind(&DeleteOriginOnFileTaskRunner, 1935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) file_system_context_, 1945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) origin, 1955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) fs_type), 1965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) callback); 1975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 19903b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)bool FileSystemQuotaClient::DoesSupport( 20003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles) storage::StorageType storage_type) const { 20158537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) FileSystemType type = QuotaStorageTypeToFileSystemType(storage_type); 20258537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) DCHECK(type != kFileSystemTypeUnknown); 20358537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) return file_system_context_->IsSandboxFileSystem(type); 20458537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles)} 20558537e28ecd584eab876aee8be7156509866d23aTorne (Richard Coles) 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)base::SequencedTaskRunner* FileSystemQuotaClient::file_task_runner() const { 207a36e5920737c6adbddd3e43b760e5de8431db6e0Torne (Richard Coles) return file_system_context_->default_file_task_runner(); 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 21003b57e008b61dfcb1fbad3aea950ae0e001748b0Torne (Richard Coles)} // namespace storage 211