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
25namespace {
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.
28base::FilePath g_cached_filepath;  // guarded by g_file_lock_
29base::File* g_cached_file = NULL;  // guarded by g_file_lock_
30uint64 g_cached_data_offset = -1;  // guarded by g_file_lock_
31uint64 g_cached_data_length = -1;  // guarded by g_file_lock_
32
33// Guards g_cached_filepath
34base::LazyInstance<base::Lock> g_file_lock_;
35}  // namespace
36
37namespace translate {
38
39// Implementation of the static factory method from BrowserCldDataProvider,
40// hooking up this specific implementation for all of Chromium.
41BrowserCldDataProvider* CreateBrowserCldDataProviderFor(
42    content::WebContents* web_contents) {
43  VLOG(1) << "Creating DataFileBrowserCldDataProvider";
44  return new DataFileBrowserCldDataProvider(web_contents);
45}
46
47void 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
59base::FilePath GetCldDataFilePath() {
60  base::AutoLock lock(g_file_lock_.Get());
61  return g_cached_filepath;
62}
63
64DataFileBrowserCldDataProvider::DataFileBrowserCldDataProvider(
65    content::WebContents* web_contents)
66    : web_contents_(web_contents), weak_pointer_factory_() {
67}
68
69DataFileBrowserCldDataProvider::~DataFileBrowserCldDataProvider() {
70}
71
72bool 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
82void 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
124void 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
139void 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
180void 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