diff options
author | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-02 22:53:18 +0000 |
---|---|---|
committer | hclam@chromium.org <hclam@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-03-02 22:53:18 +0000 |
commit | e7f2964e84ca06d711d33723e7725d03ee2aa136 (patch) | |
tree | e2cc8f033863a972998fdd668dd698d222bea3aa | |
parent | e9e6b1c6d3870e9a1e9a8aa7e6c454cdcd354d0f (diff) | |
download | chromium_src-e7f2964e84ca06d711d33723e7725d03ee2aa136.zip chromium_src-e7f2964e84ca06d711d33723e7725d03ee2aa136.tar.gz chromium_src-e7f2964e84ca06d711d33723e7725d03ee2aa136.tar.bz2 |
Proposed change to support resource loading for media files.
Highlights of changes:
- Added methods to disk_cache::Entry:
- Entry::PrepareTargetAsExternalFile(int index)
Prepare a stream in an entry to use external file for storage.
- Entry::GetExternalFile(int index)
Get the external file backing the stream in the entry.
- Added a property "CacheType type_" to HttpCache, along with setter and getter.
There shall be two cache types, COMMON_CACHE and MEDIA_CACHE for distinguishing between different purpose of HttpCache. We have this property to trigger special behavior for caching needs of media files.
- Added static methods to ChromeURLRequestContext
- ChromeURLRequestContext::CreateOriginalForMedia
Create a URLRequestContext for media files for the original profile.
- ChromeURLRequestContext::CreateOffTheRecordForMedia
Create a URLRequestContext for media files for off the record profile.
- Added method to Profile interface.
- GetRequestContextForMedia
To get the request context for media files from the context.
Design decissions:
- Enforce writing to external file by calling methods to Entry rather than construct Backend by a different flag.
Since we only want a valid and full response to go into an external file rather than redirection response or erroneous response, we should let HttpCache::Transaction to decide when to have an external file for response data. We eliminate a lot of useless external cache files.
- Adding the CacheType enum and property to HttpCache, we could allow possible (?) future extensions to HttpCache to handle other different caching needs. And there's no need to add change constructors of HttpCache, but maybe we should add a specific constructor to accomodate a media HttpCache?
- Adding Profile::GetRequestContextForMedia()
Since we will need to use this new request context in ResourceDispatcherHost, I think the best place to keep it is in the profile. Also we will expose to user that there's a separate cache for media, so it's better to expose it in the Profile level to allow settings to the media cache, e.g. max file size, etc.
Review URL: http://codereview.chromium.org/19747
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@10745 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r-- | chrome/browser/net/chrome_url_request_context.cc | 50 | ||||
-rw-r--r-- | chrome/browser/net/chrome_url_request_context.h | 10 | ||||
-rw-r--r-- | chrome/browser/profile.cc | 31 | ||||
-rw-r--r-- | chrome/browser/profile.h | 7 | ||||
-rw-r--r-- | chrome/common/chrome_constants.cc | 3 | ||||
-rw-r--r-- | chrome/common/chrome_constants.h | 2 | ||||
-rw-r--r-- | chrome/test/testing_profile.h | 3 | ||||
-rw-r--r-- | net/disk_cache/disk_cache.h | 24 | ||||
-rw-r--r-- | net/disk_cache/entry_impl.cc | 55 | ||||
-rw-r--r-- | net/disk_cache/entry_impl.h | 8 | ||||
-rw-r--r-- | net/disk_cache/entry_unittest.cc | 98 | ||||
-rw-r--r-- | net/disk_cache/mem_entry_impl.h | 8 | ||||
-rw-r--r-- | net/http/http_cache.cc | 49 | ||||
-rw-r--r-- | net/http/http_cache.h | 27 | ||||
-rw-r--r-- | net/http/http_cache_unittest.cc | 99 | ||||
-rw-r--r-- | net/http/http_network_layer.cc | 28 | ||||
-rw-r--r-- | net/http/http_network_layer.h | 12 | ||||
-rw-r--r-- | net/http/http_response_info.h | 5 | ||||
-rw-r--r-- | webkit/glue/resource_loader_bridge.h | 2 |
19 files changed, 505 insertions, 16 deletions
diff --git a/chrome/browser/net/chrome_url_request_context.cc b/chrome/browser/net/chrome_url_request_context.cc index d98f2d6..748c497 100644 --- a/chrome/browser/net/chrome_url_request_context.cc +++ b/chrome/browser/net/chrome_url_request_context.cc @@ -16,6 +16,7 @@ #include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" #include "net/http/http_cache.h" +#include "net/http/http_network_layer.h" #include "net/http/http_util.h" #include "net/proxy/proxy_service.h" #include "webkit/glue/webkit_glue.h" @@ -76,6 +77,46 @@ ChromeURLRequestContext* ChromeURLRequestContext::CreateOriginal( } // static +ChromeURLRequestContext* ChromeURLRequestContext::CreateOriginalForMedia( + Profile* profile, const FilePath& disk_cache_path) { + DCHECK(!profile->IsOffTheRecord()); + URLRequestContext* original_context = + profile->GetOriginalProfile()->GetRequestContext(); + ChromeURLRequestContext* context = new ChromeURLRequestContext(profile); + // Share the same proxy service of the common profile. + context->proxy_service_ = original_context->proxy_service(); + // Also share the cookie store of the common profile. + context->cookie_store_ = original_context->cookie_store(); + + // Create a media cache with maximum size of kint32max (2GB). + // TODO(hclam): make the maximum size of media cache configurable. + net::HttpCache* original_cache = + original_context->http_transaction_factory()->GetCache(); + net::HttpCache* cache; + if (original_cache) { + // Try to reuse HttpNetworkSession in the original context, assuming that + // HttpTransactionFactory (network_layer()) of HttpCache is implemented + // by HttpNetworkLayer so we can reuse HttpNetworkSession within it. This + // assumption will be invalid if the original HttpCache is constructed with + // HttpCache(HttpTransactionFactory*, disk_cache::Backend*) constructor. + net::HttpNetworkLayer* original_network_layer = + static_cast<net::HttpNetworkLayer*>(original_cache->network_layer()); + cache = new net::HttpCache(original_network_layer->GetSession(), + disk_cache_path.ToWStringHack(), kint32max); + } else { + // If original HttpCache doesn't exist, simply construct one with a whole + // new set of network stack. + cache = new net::HttpCache(original_context->proxy_service(), + disk_cache_path.ToWStringHack(), kint32max); + } + // Set the cache type to media. + cache->set_type(net::HttpCache::MEDIA); + + context->http_transaction_factory_ = cache; + return context; +} + +// static ChromeURLRequestContext* ChromeURLRequestContext::CreateOffTheRecord( Profile* profile) { DCHECK(profile->IsOffTheRecord()); @@ -94,6 +135,15 @@ ChromeURLRequestContext* ChromeURLRequestContext::CreateOffTheRecord( return context; } +// static +ChromeURLRequestContext* ChromeURLRequestContext::CreateOffTheRecordForMedia( + Profile* profile, const FilePath& disk_cache_path) { + // TODO(hclam): since we don't have an implementation of disk cache backend + // for media files in OTR mode, we use the original context first. Change this + // to the proper backend later. + return CreateOriginalForMedia(profile, disk_cache_path); +} + ChromeURLRequestContext::ChromeURLRequestContext(Profile* profile) : prefs_(profile->GetPrefs()), is_off_the_record_(profile->IsOffTheRecord()) { diff --git a/chrome/browser/net/chrome_url_request_context.h b/chrome/browser/net/chrome_url_request_context.h index 15d53e8..df8cc9b 100644 --- a/chrome/browser/net/chrome_url_request_context.h +++ b/chrome/browser/net/chrome_url_request_context.h @@ -28,10 +28,20 @@ class ChromeURLRequestContext : public URLRequestContext, Profile* profile, const FilePath& cookie_store_path, const FilePath& disk_cache_path); + // Create an instance for an original profile for media. This is expected to + // get called on UI thread. This method takes a profile and reuses the + // 'original' URLRequestContext for common files. + static ChromeURLRequestContext* CreateOriginalForMedia(Profile *profile, + const FilePath& disk_cache_path); + // Create an instance for use with an OTR profile. This is expected to get // called on the UI thread. static ChromeURLRequestContext* CreateOffTheRecord(Profile* profile); + // Create an instance of request context for OTR profile for media resources. + static ChromeURLRequestContext* CreateOffTheRecordForMedia(Profile* profile, + const FilePath& disk_cache_path); + // Clean up UI thread resources. This is expected to get called on the UI // thread before the instance is deleted on the IO thread. void CleanupOnUIThread(); diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc index 7c7041e..710949f 100644 --- a/chrome/browser/profile.cc +++ b/chrome/browser/profile.cc @@ -207,6 +207,20 @@ class OffTheRecordProfileImpl : public Profile, return request_context_; } + virtual URLRequestContext* GetRequestContextForMedia() { + if (!media_request_context_) { + FilePath cache_path = GetPath(); + cache_path.Append(chrome::kOffTheRecordMediaCacheDirname); + media_request_context_ = + ChromeURLRequestContext::CreateOffTheRecordForMedia( + this, cache_path); + media_request_context_->AddRef(); + + DCHECK(media_request_context_->cookie_store()); + } + return media_request_context_; + } + virtual SessionService* GetSessionService() { // Don't save any sessions when off the record. return NULL; @@ -311,6 +325,9 @@ class OffTheRecordProfileImpl : public Profile, // The context to use for requests made from this OTR session. ChromeURLRequestContext* request_context_; + // The context for requests for media resources. + ChromeURLRequestContext* media_request_context_; + // The download manager that only stores downloaded items in memory. scoped_refptr<DownloadManager> download_manager_; @@ -586,6 +603,20 @@ URLRequestContext* ProfileImpl::GetRequestContext() { return request_context_; } +URLRequestContext* ProfileImpl::GetRequestContextForMedia() { + if (!media_request_context_) { + FilePath cache_path = GetPath(); + cache_path.Append(chrome::kMediaCacheDirname); + media_request_context_ = ChromeURLRequestContext::CreateOriginalForMedia( + this, cache_path); + media_request_context_->AddRef(); + + DCHECK(media_request_context_->cookie_store()); + } + + return media_request_context_; +} + HistoryService* ProfileImpl::GetHistoryService(ServiceAccessType sat) { if (!history_service_created_) { history_service_created_ = true; diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h index 8f513ae..8c93886 100644 --- a/chrome/browser/profile.h +++ b/chrome/browser/profile.h @@ -162,6 +162,10 @@ class Profile { // keep it alive longer than the profile) must Release() it on the I/O thread. virtual URLRequestContext* GetRequestContext() = 0; + // Returns the request context for media resources asociated with this + // profile. + virtual URLRequestContext* GetRequestContextForMedia() = 0; + // Returns the session service for this profile. This may return NULL. If // this profile supports a session service (it isn't off the record), and // the session service hasn't yet been created, this forces creation of @@ -277,6 +281,7 @@ class ProfileImpl : public Profile, virtual DownloadManager* GetDownloadManager(); virtual bool HasCreatedDownloadManager() const; virtual URLRequestContext* GetRequestContext(); + virtual URLRequestContext* GetRequestContextForMedia(); virtual SessionService* GetSessionService(); virtual void ShutdownSessionService(); virtual bool HasSessionService() const; @@ -342,6 +347,8 @@ class ProfileImpl : public Profile, ChromeURLRequestContext* request_context_; + ChromeURLRequestContext* media_request_context_; + scoped_refptr<DownloadManager> download_manager_; scoped_refptr<HistoryService> history_service_; scoped_refptr<WebDataService> web_data_service_; diff --git a/chrome/common/chrome_constants.cc b/chrome/common/chrome_constants.cc index 94319c8..5deca64 100644 --- a/chrome/common/chrome_constants.cc +++ b/chrome/common/chrome_constants.cc @@ -32,6 +32,9 @@ const FilePath::CharType kExtensionFileExtension[] = FPL("crx"); // filenames const wchar_t kArchivedHistoryFilename[] = L"Archived History"; const FilePath::CharType kCacheDirname[] = FPL("Cache"); +const FilePath::CharType kMediaCacheDirname[] = FPL("Media Cache"); +const FilePath::CharType kOffTheRecordMediaCacheDirname[] = + FPL("Incognito Media Cache"); const wchar_t kChromePluginDataDirname[] = L"Plugin Data"; const FilePath::CharType kCookieFilename[] = FPL("Cookies"); const FilePath::CharType kHistoryFilename[] = FPL("History"); diff --git a/chrome/common/chrome_constants.h b/chrome/common/chrome_constants.h index 07933f4..a5d6cd0 100644 --- a/chrome/common/chrome_constants.h +++ b/chrome/common/chrome_constants.h @@ -26,6 +26,8 @@ extern const FilePath::CharType kExtensionFileExtension[]; // filenames extern const wchar_t kArchivedHistoryFilename[]; extern const FilePath::CharType kCacheDirname[]; +extern const FilePath::CharType kMediaCacheDirname[]; +extern const FilePath::CharType kOffTheRecordMediaCacheDirname[]; extern const wchar_t kChromePluginDataDirname[]; extern const FilePath::CharType kCookieFilename[]; extern const FilePath::CharType kHistoryFilename[]; diff --git a/chrome/test/testing_profile.h b/chrome/test/testing_profile.h index 43913ad..4601173 100644 --- a/chrome/test/testing_profile.h +++ b/chrome/test/testing_profile.h @@ -123,6 +123,9 @@ class TestingProfile : public Profile { virtual URLRequestContext* GetRequestContext() { return NULL; } + virtual URLRequestContext* GetRequestContextForMedia() { + return NULL; + } void set_session_service(SessionService* session_service) { session_service_ = session_service; } diff --git a/net/disk_cache/disk_cache.h b/net/disk_cache/disk_cache.h index 59d2ad2..7d6e41a 100644 --- a/net/disk_cache/disk_cache.h +++ b/net/disk_cache/disk_cache.h @@ -12,6 +12,7 @@ #include <vector> #include "base/basictypes.h" +#include "base/platform_file.h" #include "base/time.h" #include "net/base/completion_callback.h" @@ -153,6 +154,29 @@ class Entry { net::CompletionCallback* completion_callback, bool truncate) = 0; + // Prepares a target stream as an external file, returns a corresponding + // base::PlatformFile if successful, returns base::kInvalidPlatformFileValue + // if fails. If this call returns a valid base::PlatformFile value (i.e. + // not base::kInvalidPlatformFileValue), there is no guarantee that the file + // is truncated. Implementor can always return base::kInvalidPlatformFileValue + // if external file is not available in that particular implementation. + // Caller should never close the file handle returned by this method, since + // the handle should be managed by the implementor of this class. Caller + // should never save the handle for future use. + // With a stream prepared as an external file, the stream would always be + // kept in an external file since creation, even if the stream has 0 bytes. + // So we need to be cautious about using this option for preparing a stream or + // we will end up having a lot of empty cache files. Calling this method also + // means that all data written to the stream will always be written to file + // directly *without* buffering. + virtual base::PlatformFile UseExternalFile(int index) = 0; + + // Returns a read file handle for the cache stream referenced by |index|. + // Caller should never close the handle returned by this method and should + // not save it for future use. The lifetime of the base::PlatformFile handle + // is managed by the implementor of this class. + virtual base::PlatformFile GetPlatformFile(int index) = 0; + protected: virtual ~Entry() {} }; diff --git a/net/disk_cache/entry_impl.cc b/net/disk_cache/entry_impl.cc index b64073a..9db5647 100644 --- a/net/disk_cache/entry_impl.cc +++ b/net/disk_cache/entry_impl.cc @@ -78,8 +78,10 @@ EntryImpl::EntryImpl(BackendImpl* backend, Addr address) entry_.LazyInit(backend->File(address), address); doomed_ = false; backend_ = backend; - for (int i = 0; i < NUM_STREAMS; i++) + for (int i = 0; i < NUM_STREAMS; i++) { unreported_size_[i] = 0; + need_file_[i] = false; + } } // When an entry is deleted from the cache, we clean up all the data associated @@ -318,7 +320,9 @@ int EntryImpl::WriteData(int index, int offset, net::IOBuffer* buf, int buf_len, backend_->OnEvent(Stats::WRITE_DATA); - if (user_buffers_[index].get()) { + // If we have prepared the cache as an external file, we should never use + // user_buffers_ and always write to file directly. + if (!need_file_[index] && user_buffers_[index].get()) { // Complete the operation locally. if (!buf_len) return 0; @@ -365,6 +369,43 @@ int EntryImpl::WriteData(int index, int offset, net::IOBuffer* buf, int buf_len, return (completed || !completion_callback) ? buf_len : net::ERR_IO_PENDING; } +base::PlatformFile EntryImpl::UseExternalFile(int index) { + DCHECK(index >= 0 && index < NUM_STREAMS); + + Addr address(entry_.Data()->data_addr[index]); + + // We will not prepare the cache file since the entry is already initialized, + // just return the platform file backing the cache. + if (address.is_initialized()) + return GetPlatformFile(index); + + if (!backend_->CreateExternalFile(&address)) + return base::kInvalidPlatformFileValue; + + entry_.Data()->data_addr[index] = address.value(); + entry_.Store(); + + // Set the flag for this stream so we never use user_buffer_. + // TODO(hclam): do we need to save this information to EntryStore? + need_file_[index] = true; + + return GetPlatformFile(index); +} + +base::PlatformFile EntryImpl::GetPlatformFile(int index) { + DCHECK(index >= 0 && index < NUM_STREAMS); + + Addr address(entry_.Data()->data_addr[index]); + if (!address.is_initialized() || !address.is_separate_file()) + return base::kInvalidPlatformFileValue; + + File* cache_file = GetExternalFile(address, index); + if (!cache_file) + return base::kInvalidPlatformFileValue; + + return cache_file->platform_file(); +} + uint32 EntryImpl::GetHash() { return entry_.Data()->hash; } @@ -603,6 +644,16 @@ File* EntryImpl::GetExternalFile(Addr address, int index) { bool EntryImpl::PrepareTarget(int index, int offset, int buf_len, bool truncate) { Addr address(entry_.Data()->data_addr[index]); + + // If we are instructed to use an external file, we should never buffer when + // writing. We are done with preparation of the target automatically, since + // we have already created the external file for writing. + if (need_file_[index]) { + // Make sure the stream is initialized and is kept in an external file. + DCHECK(address.is_initialized() && address.is_separate_file()); + return true; + } + if (address.is_initialized() || user_buffers_[index].get()) return GrowUserBuffer(index, offset, buf_len, truncate); diff --git a/net/disk_cache/entry_impl.h b/net/disk_cache/entry_impl.h index 0b08a58..6d3d614 100644 --- a/net/disk_cache/entry_impl.h +++ b/net/disk_cache/entry_impl.h @@ -32,6 +32,8 @@ class EntryImpl : public Entry, public base::RefCounted<EntryImpl> { virtual int WriteData(int index, int offset, net::IOBuffer* buf, int buf_len, net::CompletionCallback* completion_callback, bool truncate); + virtual base::PlatformFile UseExternalFile(int index); + virtual base::PlatformFile GetPlatformFile(int index); inline CacheEntryBlock* entry() { return &entry_; @@ -45,8 +47,7 @@ class EntryImpl : public Entry, public base::RefCounted<EntryImpl> { // Performs the initialization of a EntryImpl that will be added to the // cache. - bool CreateEntry(Addr node_address, const std::string& key, - uint32 hash); + bool CreateEntry(Addr node_address, const std::string& key, uint32 hash); // Returns true if this entry matches the lookup arguments. bool IsSameEntry(const std::string& key, uint32 hash); @@ -140,7 +141,8 @@ class EntryImpl : public Entry, public base::RefCounted<EntryImpl> { // data and key. int unreported_size_[NUM_STREAMS]; // Bytes not reported yet to the backend. bool doomed_; // True if this entry was removed from the cache. - + bool need_file_[NUM_STREAMS]; // True if stream is prepared as an external + // file. DISALLOW_EVIL_CONSTRUCTORS(EntryImpl); }; diff --git a/net/disk_cache/entry_unittest.cc b/net/disk_cache/entry_unittest.cc index 47b70d0..bb7cc2e 100644 --- a/net/disk_cache/entry_unittest.cc +++ b/net/disk_cache/entry_unittest.cc @@ -833,3 +833,101 @@ TEST_F(DiskCacheEntryTest, MemoryOnlyDoomedEntry) { DoomEntry(); } +// Check that we can hint an entry to use external file and the return value +// is a valid file handle. +TEST_F(DiskCacheEntryTest, UseExternalFile) { + InitCache(); + + disk_cache::Entry* entry; + ASSERT_TRUE(cache_->CreateEntry("key", &entry)); + base::PlatformFile cache_file = entry->UseExternalFile(0); + + // We should have a valid file handle. + EXPECT_NE(base::kInvalidPlatformFileValue, cache_file); + scoped_refptr<disk_cache::File> file(new disk_cache::File(cache_file)); + + // 4KB. + size_t kDataSize = 0x1000; + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kDataSize); + + CacheTestFillBuffer(buffer->data(), kDataSize, false); + ASSERT_EQ(0U, file->GetLength()); + ASSERT_EQ(kDataSize, static_cast<size_t>( + entry->WriteData(0, 0, buffer, kDataSize, NULL, false))); + ASSERT_EQ(kDataSize, file->GetLength()); + entry->Close(); +} + +// Make sure we can use Entry::GetPlatformFile on an entry stored in an external +// file and get a valid file handle. +TEST_F(DiskCacheEntryTest, GetPlatformFile) { + InitCache(); + + disk_cache::Entry* entry; + ASSERT_TRUE(cache_->CreateEntry("key", &entry)); + EXPECT_NE(base::kInvalidPlatformFileValue, entry->UseExternalFile(0)); + + size_t kDataSize = 50; + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kDataSize); + + // Fill the data buffer and write it to cache. + CacheTestFillBuffer(buffer->data(), kDataSize, false); + ASSERT_EQ(kDataSize,static_cast<size_t>( + entry->WriteData(0, 0, buffer, kDataSize, NULL, false))); + + // Close the entry. + entry->Close(); + + // Open the entry again and get it's file handle. + ASSERT_TRUE(cache_->OpenEntry("key", &entry)); + base::PlatformFile cache_file = entry->GetPlatformFile(0); + + // Make sure it's a valid file handle and verify the size of the file. + scoped_refptr<disk_cache::File> file(new disk_cache::File(cache_file)); + ASSERT_EQ(kDataSize, file->GetLength()); + + entry->Close(); +} + +// Test the behavior of EntryImpl that small entries are kept in block files +// or buffer, and only entries above certain size would be stored in an +// external file, make sure GetPlatformFile() works with both cases without +// using UseExternalFile(). +TEST_F(DiskCacheEntryTest, GetPlatformFileVariableEntrySize) { + InitCache(); + + disk_cache::Entry* entry; + + // Make the buffer just larger than disk_cache::kMaxBlockSize. + const size_t kLargeDataSize = disk_cache::kMaxBlockSize + 1; + const size_t kSmallDataSize = kLargeDataSize / 2; + scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(kLargeDataSize); + + // 1. First test with small entry. + ASSERT_TRUE(cache_->CreateEntry("small_entry", &entry)); + + CacheTestFillBuffer(buffer->data(), kSmallDataSize, false); + ASSERT_EQ(kSmallDataSize, static_cast<size_t>( + entry->WriteData(0, 0, buffer, kSmallDataSize, NULL, false))); + + // Make sure we don't get an external file. + ASSERT_EQ(base::kInvalidPlatformFileValue, entry->GetPlatformFile(0)); + + entry->Close(); + + // 2. Test with large entry. + ASSERT_TRUE(cache_->CreateEntry("large_entry", &entry)); + + CacheTestFillBuffer(buffer->data(), kLargeDataSize, false); + ASSERT_EQ(kLargeDataSize, static_cast<size_t>( + entry->WriteData(0, 0, buffer, kLargeDataSize, NULL, false))); + + base::PlatformFile cache_file = entry->GetPlatformFile(0); + EXPECT_NE(base::kInvalidPlatformFileValue, cache_file); + + // Make sure it's a valid file handle and verify the size of the file. + scoped_refptr<disk_cache::File> file(new disk_cache::File(cache_file)); + ASSERT_EQ(kLargeDataSize, file->GetLength()); + + entry->Close(); +} diff --git a/net/disk_cache/mem_entry_impl.h b/net/disk_cache/mem_entry_impl.h index cd8c6e7..63235f9 100644 --- a/net/disk_cache/mem_entry_impl.h +++ b/net/disk_cache/mem_entry_impl.h @@ -29,6 +29,14 @@ class MemEntryImpl : public Entry { virtual int WriteData(int index, int offset, net::IOBuffer* buf, int buf_len, net::CompletionCallback* completion_callback, bool truncate); + virtual base::PlatformFile UseExternalFile(int index) { + // MemEntryImpl doesn't support caching to an external file. + return base::kInvalidPlatformFileValue; + } + virtual base::PlatformFile GetPlatformFile(int index) { + // MemEntryImpl doesn't support caching to an external file. + return base::kInvalidPlatformFileValue; + } // Performs the initialization of a EntryImpl that will be added to the // cache. diff --git a/net/http/http_cache.cc b/net/http/http_cache.cc index c54b6d4..913dc00 100644 --- a/net/http/http_cache.cc +++ b/net/http/http_cache.cc @@ -286,6 +286,9 @@ class HttpCache::Transaction // Called to write response_ to the cache entry. void WriteResponseInfoToEntry(); + // Called to truncate response content in the entry. + void TruncateResponseData(); + // Called to append response data to the cache entry. void AppendResponseDataToEntry(IOBuffer* data, int data_len); @@ -790,6 +793,12 @@ int HttpCache::Transaction::ReadResponseInfoFromEntry() { if (!HttpCache::ReadResponseInfo(entry_->disk_entry, &response_)) return ERR_FAILED; + // If the cache object is used for media file, we want the file handle of + // response data. + if (cache_->type() == HttpCache::MEDIA) + response_.response_data_file = + entry_->disk_entry->GetPlatformFile(kResponseContentIndex); + return OK; } @@ -846,6 +855,27 @@ void HttpCache::Transaction::AppendResponseDataToEntry(IOBuffer* data, WriteToEntry(kResponseContentIndex, current_size, data, data_len); } +void HttpCache::Transaction::TruncateResponseData() { + if (!entry_) + return; + + // If the cache is for media files, we try to prepare the response data + // file as an external file and truncate it afterwards. + // Recipient of ResponseInfo should judge from |response_.response_data_file| + // to tell whether an external file of response data is available for reading + // or not. + // TODO(hclam): we should prepare the target stream as extern file only + // if we get a valid response from server, i.e. 200. We don't want empty + // cache files for redirection or external files for erroneous requests. + response_.response_data_file = base::kInvalidPlatformFileValue; + if (cache_->type() == HttpCache::MEDIA) + response_.response_data_file = + entry_->disk_entry->UseExternalFile(kResponseContentIndex); + + // Truncate the stream. + WriteToEntry(kResponseContentIndex, 0, NULL, 0); +} + void HttpCache::Transaction::DoneWritingToEntry(bool success) { if (!entry_) return; @@ -902,8 +932,8 @@ void HttpCache::Transaction::OnNetworkInfoAvailable(int result) { response_ = *new_response; WriteResponseInfoToEntry(); - // Truncate the response data - WriteToEntry(kResponseContentIndex, 0, NULL, 0); + // Truncate response data + TruncateResponseData(); // If this response is a redirect, then we can stop writing now. (We // don't need to cache the response body of a redirect.) @@ -958,14 +988,28 @@ HttpCache::HttpCache(ProxyService* proxy_service, int cache_size) : disk_cache_dir_(cache_dir), mode_(NORMAL), + type_(COMMON), network_layer_(HttpNetworkLayer::CreateFactory(proxy_service)), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), in_memory_cache_(false), cache_size_(cache_size) { } +HttpCache::HttpCache(HttpNetworkSession* session, + const std::wstring& cache_dir, + int cache_size) + : disk_cache_dir_(cache_dir), + mode_(NORMAL), + type_(COMMON), + network_layer_(HttpNetworkLayer::CreateFactory(session)), + ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), + in_memory_cache_(false), + cache_size_(cache_size) { +} + HttpCache::HttpCache(ProxyService* proxy_service, int cache_size) : mode_(NORMAL), + type_(COMMON), network_layer_(HttpNetworkLayer::CreateFactory(proxy_service)), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), in_memory_cache_(true), @@ -975,6 +1019,7 @@ HttpCache::HttpCache(ProxyService* proxy_service, int cache_size) HttpCache::HttpCache(HttpTransactionFactory* network_layer, disk_cache::Backend* disk_cache) : mode_(NORMAL), + type_(COMMON), network_layer_(network_layer), disk_cache_(disk_cache), ALLOW_THIS_IN_INITIALIZER_LIST(task_factory_(this)), diff --git a/net/http/http_cache.h b/net/http/http_cache.h index 51991f5..528c964 100644 --- a/net/http/http_cache.h +++ b/net/http/http_cache.h @@ -29,6 +29,7 @@ class Entry; namespace net { +class HttpNetworkSession; class HttpRequestInfo; class HttpResponseInfo; class ProxyService; @@ -48,13 +49,33 @@ class HttpCache : public HttpTransactionFactory { PLAYBACK }; - // Initialize the cache from the directory where its data is stored. The + // The type of an HttpCache object, essentially describe what an HttpCache + // object is for. + enum Type { + // An HttpCache object for common objects, e.g. html pages, images, fonts, + // css files, js files and other common web resources. + COMMON = 0, + // A cache system for media file, e.g. video and audio files. These files + // are huge and has special requirement for access. + MEDIA + }; + + // Initialize the cache from the directory where its data is stored. The // disk cache is initialized lazily (by CreateTransaction) in this case. If // |cache_size| is zero, a default value will be calculated automatically. HttpCache(ProxyService* proxy_service, const std::wstring& cache_dir, int cache_size); + // Initialize the cache from the directory where its data is stored. The + // disk cache is initialized lazily (by CreateTransaction) in this case. If + // |cache_size| is zero, a default value will be calculated automatically. + // Provide an existing HttpNetworkSession, the cache can construct a + // network layer with a shared HttpNetworkSession in order for multiple + // network layers to share information (e.g. authenication data). + HttpCache(HttpNetworkSession* session, const std::wstring& cache_dir, + int cache_size); + // Initialize using an in-memory cache. The cache is initialized lazily // (by CreateTransaction) in this case. If |cache_size| is zero, a default // value will be calculated automatically. @@ -91,6 +112,9 @@ class HttpCache : public HttpTransactionFactory { void set_mode(Mode value) { mode_ = value; } Mode mode() { return mode_; } + void set_type(Type type) { type_ = type; } + Type type() { return type_; } + private: // Types -------------------------------------------------------------------- @@ -146,6 +170,7 @@ class HttpCache : public HttpTransactionFactory { std::wstring disk_cache_dir_; Mode mode_; + Type type_; scoped_ptr<HttpTransactionFactory> network_layer_; scoped_ptr<disk_cache::Backend> disk_cache_; diff --git a/net/http/http_cache_unittest.cc b/net/http/http_cache_unittest.cc index b52f2a4..5db229b 100644 --- a/net/http/http_cache_unittest.cc +++ b/net/http/http_cache_unittest.cc @@ -6,6 +6,7 @@ #include "base/hash_tables.h" #include "base/message_loop.h" +#include "base/platform_file.h" #include "base/string_util.h" #include "net/base/net_errors.h" #include "net/base/load_flags.h" @@ -26,10 +27,12 @@ namespace { class MockDiskEntry : public disk_cache::Entry, public base::RefCounted<MockDiskEntry> { public: - MockDiskEntry() : test_mode_(0), doomed_(false) { + MockDiskEntry() + : test_mode_(0), doomed_(false), platform_file_(global_platform_file_) { } - MockDiskEntry(const std::string& key) : key_(key), doomed_(false) { + MockDiskEntry(const std::string& key) + : key_(key), doomed_(false), platform_file_(global_platform_file_) { const MockTransaction* t = FindMockTransaction(GURL(key)); DCHECK(t); test_mode_ = t->test_mode; @@ -98,6 +101,18 @@ class MockDiskEntry : public disk_cache::Entry, return buf_len; } + base::PlatformFile UseExternalFile(int index) { + return platform_file_; + } + + base::PlatformFile GetPlatformFile(int index) { + return platform_file_; + } + + static void set_global_platform_file(base::PlatformFile platform_file) { + global_platform_file_ = platform_file; + } + private: // Unlike the callbacks for MockHttpTransaction, we want this one to run even // if the consumer called Close on the MockDiskEntry. We achieve that by @@ -114,8 +129,13 @@ class MockDiskEntry : public disk_cache::Entry, std::vector<char> data_[2]; int test_mode_; bool doomed_; + base::PlatformFile platform_file_; + static base::PlatformFile global_platform_file_; }; +base::PlatformFile MockDiskEntry::global_platform_file_ = + base::kInvalidPlatformFileValue; + class MockDiskCache : public disk_cache::Backend { public: MockDiskCache() : open_count_(0), create_count_(0), fail_requests_(0) { @@ -1184,3 +1204,78 @@ TEST(HttpCache, OutlivedTransactions) { delete cache; delete trans; } + +// Make sure Entry::UseExternalFile is called when a new entry is created in +// a HttpCache with MEDIA type. Also make sure Entry::GetPlatformFile is called +// when an entry is loaded from a HttpCache with MEDIA type. Also confirm we +// will receive a file handle in ResponseInfo from a media cache. +TEST(HttpCache, SimpleGET_MediaCache) { + // Initialize the HttpCache with MEDIA type. + MockHttpCache cache; + cache.http_cache()->set_type(net::HttpCache::MEDIA); + + // Define some fake file handles for testing. + base::PlatformFile kFakePlatformFile1, kFakePlatformFile2; +#if defined(OS_WIN) + kFakePlatformFile1 = reinterpret_cast<base::PlatformFile>(1); + kFakePlatformFile2 = reinterpret_cast<base::PlatformFile>(2); +#else + kFakePlatformFile1 = 1; + kFakePlatformFile2 = 2; +#endif + + ScopedMockTransaction trans_info(kSimpleGET_Transaction); + TestCompletionCallback callback; + + { + // Set the fake file handle to MockDiskEntry so cache is written with an + // entry created with our fake file handle. + MockDiskEntry::set_global_platform_file(kFakePlatformFile1); + + scoped_ptr<net::HttpTransaction> trans( + cache.http_cache()->CreateTransaction()); + ASSERT_TRUE(trans.get()); + + MockHttpRequest request(trans_info); + + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::OK, rv); + + const net::HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response); + + ASSERT_EQ(kFakePlatformFile1, response->response_data_file); + + ReadAndVerifyTransaction(trans.get(), trans_info); + } + + // Load only from cache so we would get the same file handle. + trans_info.load_flags |= net::LOAD_ONLY_FROM_CACHE; + + { + // Set a different file handle value to MockDiskEntry so any new entry + // created in the cache won't have the same file handle value. + MockDiskEntry::set_global_platform_file(kFakePlatformFile2); + + scoped_ptr<net::HttpTransaction> trans( + cache.http_cache()->CreateTransaction()); + ASSERT_TRUE(trans.get()); + + MockHttpRequest request(trans_info); + + int rv = trans->Start(&request, &callback); + if (rv == net::ERR_IO_PENDING) + rv = callback.WaitForResult(); + ASSERT_EQ(net::OK, rv); + + const net::HttpResponseInfo* response = trans->GetResponseInfo(); + ASSERT_TRUE(response); + + // Make sure we get the same file handle as in the first request. + ASSERT_EQ(kFakePlatformFile1, response->response_data_file); + + ReadAndVerifyTransaction(trans.get(), trans_info); + } +} diff --git a/net/http/http_network_layer.cc b/net/http/http_network_layer.cc index 23f53f7..8ba6bfc 100644 --- a/net/http/http_network_layer.cc +++ b/net/http/http_network_layer.cc @@ -21,13 +21,26 @@ HttpTransactionFactory* HttpNetworkLayer::CreateFactory( return new HttpNetworkLayer(proxy_service); } +// static +HttpTransactionFactory* HttpNetworkLayer::CreateFactory( + HttpNetworkSession* session) { + DCHECK(session); + + return new HttpNetworkLayer(session); +} + //----------------------------------------------------------------------------- HttpNetworkLayer::HttpNetworkLayer(ProxyService* proxy_service) - : proxy_service_(proxy_service), suspended_(false) { + : proxy_service_(proxy_service), session_(NULL), suspended_(false) { DCHECK(proxy_service_); } +HttpNetworkLayer::HttpNetworkLayer(HttpNetworkSession* session) + : proxy_service_(NULL), session_(session), suspended_(false) { + DCHECK(session_.get()); +} + HttpNetworkLayer::~HttpNetworkLayer() { } @@ -35,11 +48,8 @@ HttpTransaction* HttpNetworkLayer::CreateTransaction() { if (suspended_) return NULL; - if (!session_) - session_ = new HttpNetworkSession(proxy_service_); - return new HttpNetworkTransaction( - session_, ClientSocketFactory::GetDefaultFactory()); + GetSession(), ClientSocketFactory::GetDefaultFactory()); } HttpCache* HttpNetworkLayer::GetCache() { @@ -53,5 +63,13 @@ void HttpNetworkLayer::Suspend(bool suspend) { session_->connection_pool()->CloseIdleSockets(); } +HttpNetworkSession* HttpNetworkLayer::GetSession() { + if (!session_) { + DCHECK(proxy_service_); + session_ = new HttpNetworkSession(proxy_service_); + } + return session_; +} + } // namespace net diff --git a/net/http/http_network_layer.h b/net/http/http_network_layer.h index 8424b2f..a2bb491 100644 --- a/net/http/http_network_layer.h +++ b/net/http/http_network_layer.h @@ -19,17 +19,29 @@ class HttpNetworkLayer : public HttpTransactionFactory { public: // |proxy_service| must remain valid for the lifetime of HttpNetworkLayer. explicit HttpNetworkLayer(ProxyService* proxy_service); + // Construct a HttpNetworkLayer with an existing HttpNetworkSession which + // contains a valid ProxyService. + explicit HttpNetworkLayer(HttpNetworkSession* session); ~HttpNetworkLayer(); // This function hides the details of how a network layer gets instantiated // and allows other implementations to be substituted. static HttpTransactionFactory* CreateFactory(ProxyService* proxy_service); + // Create a transaction factory that instantiate a network layer over an + // existing network session. Network session contains some valuable + // information (e.g. authentication data) that we want to share across + // multiple network layers. This method exposes the implementation details + // of a network layer, use this method with an existing network layer only + // when network session is shared. + static HttpTransactionFactory* CreateFactory(HttpNetworkSession* session); // HttpTransactionFactory methods: virtual HttpTransaction* CreateTransaction(); virtual HttpCache* GetCache(); virtual void Suspend(bool suspend); + HttpNetworkSession* GetSession(); + private: // The proxy service being used for the session. ProxyService* proxy_service_; diff --git a/net/http/http_response_info.h b/net/http/http_response_info.h index 671a390..caa49df 100644 --- a/net/http/http_response_info.h +++ b/net/http/http_response_info.h @@ -5,6 +5,7 @@ #ifndef NET_HTTP_HTTP_RESPONSE_INFO_H_ #define NET_HTTP_HTTP_RESPONSE_INFO_H_ +#include "base/platform_file.h" #include "base/time.h" #include "net/base/auth.h" #include "net/base/ssl_info.h" @@ -48,6 +49,10 @@ class HttpResponseInfo { // The "Vary" header data for this response. HttpVaryData vary_data; + + // Platform specific file handle to the response data, if response data is + // not in a standalone file, its value is base::kInvalidPlatformFileValue. + base::PlatformFile response_data_file; }; } // namespace net diff --git a/webkit/glue/resource_loader_bridge.h b/webkit/glue/resource_loader_bridge.h index 2698cb4..948bc74 100644 --- a/webkit/glue/resource_loader_bridge.h +++ b/webkit/glue/resource_loader_bridge.h @@ -110,7 +110,7 @@ class ResourceLoaderBridge { virtual void OnReceivedData(const char* data, int len) = 0; // Called when the response is complete. This method signals completion of - // the resource load. + // the resource load.ff virtual void OnCompletedRequest(const URLRequestStatus& status) = 0; // Returns the URL of the request, which allows us to display it in |