1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "data_file_browser_cld_data_provider.h" 6 7 #include "base/basictypes.h" 8 #include "base/files/file.h" 9 #include "base/files/file_path.h" 10 #include "base/lazy_instance.h" 11 #include "base/logging.h" 12 #include "base/memory/weak_ptr.h" 13 #include "base/path_service.h" 14 #include "base/synchronization/lock.h" 15 #include "base/task_runner.h" 16 #include "components/translate/content/common/data_file_cld_data_provider_messages.h" 17 #include "content/public/browser/browser_thread.h" 18 #include "content/public/browser/render_process_host.h" 19 #include "content/public/browser/render_view_host.h" 20 #include "content/public/browser/web_contents.h" 21 #include "ipc/ipc_message.h" 22 #include "ipc/ipc_message_macros.h" 23 #include "ipc/ipc_platform_file.h" 24 25 namespace { 26 // The data file, cached as long as the process stays alive. 27 // We also track the offset at which the data starts, and its length. 28 base::FilePath g_cached_filepath; // guarded by g_file_lock_ 29 base::File* g_cached_file = NULL; // guarded by g_file_lock_ 30 uint64 g_cached_data_offset = -1; // guarded by g_file_lock_ 31 uint64 g_cached_data_length = -1; // guarded by g_file_lock_ 32 33 // Guards g_cached_filepath 34 base::LazyInstance<base::Lock> g_file_lock_; 35 } // namespace 36 37 namespace translate { 38 39 // Implementation of the static factory method from BrowserCldDataProvider, 40 // hooking up this specific implementation for all of Chromium. 41 BrowserCldDataProvider* CreateBrowserCldDataProviderFor( 42 content::WebContents* web_contents) { 43 VLOG(1) << "Creating DataFileBrowserCldDataProvider"; 44 return new DataFileBrowserCldDataProvider(web_contents); 45 } 46 47 void SetCldDataFilePath(const base::FilePath& path) { 48 VLOG(1) << "Setting CLD data file path to: " << path.value(); 49 base::AutoLock lock(g_file_lock_.Get()); 50 if (g_cached_filepath == path) 51 return; // no change necessary 52 g_cached_filepath = path; 53 // For sanity, clean these other values up just in case. 54 g_cached_file = NULL; 55 g_cached_data_length = -1; 56 g_cached_data_offset = -1; 57 } 58 59 base::FilePath GetCldDataFilePath() { 60 base::AutoLock lock(g_file_lock_.Get()); 61 return g_cached_filepath; 62 } 63 64 DataFileBrowserCldDataProvider::DataFileBrowserCldDataProvider( 65 content::WebContents* web_contents) 66 : web_contents_(web_contents), weak_pointer_factory_() { 67 } 68 69 DataFileBrowserCldDataProvider::~DataFileBrowserCldDataProvider() { 70 } 71 72 bool DataFileBrowserCldDataProvider::OnMessageReceived( 73 const IPC::Message& message) { 74 bool handled = true; 75 IPC_BEGIN_MESSAGE_MAP(DataFileBrowserCldDataProvider, message) 76 IPC_MESSAGE_HANDLER(ChromeViewHostMsg_NeedCldDataFile, OnCldDataRequest) 77 IPC_MESSAGE_UNHANDLED(handled = false) 78 IPC_END_MESSAGE_MAP() 79 return handled; 80 } 81 82 void DataFileBrowserCldDataProvider::OnCldDataRequest() { 83 // Quickly try to read g_cached_file. If valid, the file handle is 84 // cached and can be used immediately. Else, queue the caching task to the 85 // blocking pool. 86 VLOG(1) << "Received request for CLD data file."; 87 base::File* handle = NULL; 88 uint64 data_offset = 0; 89 uint64 data_length = 0; 90 { 91 base::AutoLock lock(g_file_lock_.Get()); 92 handle = g_cached_file; 93 data_offset = g_cached_data_offset; 94 data_length = g_cached_data_length; 95 } 96 97 if (handle && handle->IsValid()) { 98 // Cached data available. Respond to the request. 99 VLOG(1) << "CLD data file is already cached, replying immediately."; 100 SendCldDataResponseInternal(handle, data_offset, data_length); 101 return; 102 } 103 104 if (weak_pointer_factory_.get() == NULL) { 105 weak_pointer_factory_.reset( 106 new base::WeakPtrFactory<DataFileBrowserCldDataProvider>(this)); 107 weak_pointer_factory_.get()->GetWeakPtr().get(); 108 } 109 110 // Else, we don't have the data file yet. Queue a caching attempt. 111 // The caching attempt happens in the blocking pool because it may involve 112 // arbitrary filesystem access. 113 // After the caching attempt is made, we call MaybeSendCLDDataAvailable 114 // to pass the file handle to the renderer. This only results in an IPC 115 // message if the caching attempt was successful. 116 VLOG(1) << "CLD data file not yet cached, deferring lookup"; 117 content::BrowserThread::PostBlockingPoolTaskAndReply( 118 FROM_HERE, 119 base::Bind(&DataFileBrowserCldDataProvider::OnCldDataRequestInternal), 120 base::Bind(&DataFileBrowserCldDataProvider::SendCldDataResponse, 121 weak_pointer_factory_.get()->GetWeakPtr())); 122 } 123 124 void DataFileBrowserCldDataProvider::SendCldDataResponse() { 125 base::File* handle = NULL; 126 uint64 data_offset = 0; 127 uint64 data_length = 0; 128 { 129 base::AutoLock lock(g_file_lock_.Get()); 130 handle = g_cached_file; 131 data_offset = g_cached_data_offset; 132 data_length = g_cached_data_length; 133 } 134 135 if (handle && handle->IsValid()) 136 SendCldDataResponseInternal(handle, data_offset, data_length); 137 } 138 139 void DataFileBrowserCldDataProvider::SendCldDataResponseInternal( 140 const base::File* handle, 141 const uint64 data_offset, 142 const uint64 data_length) { 143 VLOG(1) << "Sending CLD data file response."; 144 145 content::RenderViewHost* render_view_host = 146 web_contents_->GetRenderViewHost(); 147 if (render_view_host == NULL) { 148 // Render view destroyed, no need to bother. 149 VLOG(1) << "Lost render view host, giving up"; 150 return; 151 } 152 153 content::RenderProcessHost* render_process_host = 154 render_view_host->GetProcess(); 155 if (render_process_host == NULL) { 156 // Render process destroyed, render view not yet dead. No need to bother. 157 VLOG(1) << "Lost render process, giving up"; 158 return; 159 } 160 161 // Data available, respond to the request. 162 const int render_process_handle = render_process_host->GetHandle(); 163 IPC::PlatformFileForTransit ipc_platform_file = 164 IPC::GetFileHandleForProcess(handle->GetPlatformFile(), 165 render_process_handle, false); 166 167 // In general, sending a response from within the code path that is processing 168 // a request is discouraged because there is potential for deadlock (if the 169 // methods are sent synchronously) or loops (if the response can trigger a 170 // new request). Neither of these concerns is relevant in this code, so 171 // sending the response from within the code path of the request handler is 172 // safe. 173 render_view_host->Send( 174 new ChromeViewMsg_CldDataFileAvailable(render_view_host->GetRoutingID(), 175 ipc_platform_file, 176 data_offset, 177 data_length)); 178 } 179 180 void DataFileBrowserCldDataProvider::OnCldDataRequestInternal() { 181 // Because this function involves arbitrary file system access, it must run 182 // on the blocking pool. 183 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 184 DCHECK(!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 185 VLOG(1) << "CLD data file caching attempt starting."; 186 187 { 188 base::AutoLock lock(g_file_lock_.Get()); 189 if (g_cached_file) { 190 VLOG(1) << "CLD data file is already cached, aborting caching attempt"; 191 return; // Already done, duplicate request 192 } 193 } 194 195 const base::FilePath path = GetCldDataFilePath(); 196 if (path.empty()) { 197 VLOG(1) << "CLD data file does not yet have a known location."; 198 return; 199 } 200 201 // If the file exists, we can send an IPC-safe construct back to the 202 // renderer process immediately; otherwise, nothing to do here. 203 if (!base::PathExists(path)) { 204 VLOG(1) << "CLD data file does not exist."; 205 return; 206 } 207 208 // Attempt to open the file for reading. 209 scoped_ptr<base::File> file( 210 new base::File(path, base::File::FLAG_OPEN | base::File::FLAG_READ)); 211 if (!file->IsValid()) { 212 LOG(WARNING) << "CLD data file exists but cannot be opened"; 213 return; 214 } 215 216 base::File::Info file_info; 217 if (!file->GetInfo(&file_info)) { 218 LOG(WARNING) << "CLD data file exists but cannot be inspected"; 219 return; 220 } 221 222 // For now, our offset and length are simply 0 and the length of the file, 223 // respectively. If we later decide to include the CLD2 data file inside of 224 // a larger binary context, these params can be twiddled appropriately. 225 const uint64 data_offset = 0; 226 const uint64 data_length = file_info.size; 227 228 { 229 base::AutoLock lock(g_file_lock_.Get()); 230 if (g_cached_file) { 231 // Idempotence: Racing another request on the blocking pool, abort. 232 VLOG(1) << "Another thread finished caching first, aborting."; 233 } else { 234 // Else, this request has taken care of it all. Cache all info. 235 VLOG(1) << "Caching CLD data file information."; 236 g_cached_file = file.release(); 237 g_cached_data_offset = data_offset; 238 g_cached_data_length = data_length; 239 } 240 } 241 } 242 243 } // namespace translate 244