1// Copyright (c) 2012 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 "content/renderer/pepper/quota_file_io.h"
6
7#include <algorithm>
8
9#include "base/bind.h"
10#include "base/location.h"
11#include "base/memory/weak_ptr.h"
12#include "base/message_loop/message_loop_proxy.h"
13#include "base/stl_util.h"
14#include "base/task_runner_util.h"
15#include "content/renderer/pepper/host_globals.h"
16
17using base::PlatformFile;
18using base::PlatformFileError;
19using quota::StorageType;
20
21namespace content {
22
23namespace {
24StorageType PPFileSystemTypeToQuotaStorageType(PP_FileSystemType type) {
25  switch (type) {
26    case PP_FILESYSTEMTYPE_LOCALPERSISTENT:
27      return quota::kStorageTypePersistent;
28    case PP_FILESYSTEMTYPE_LOCALTEMPORARY:
29      return quota::kStorageTypeTemporary;
30    default:
31      return quota::kStorageTypeUnknown;
32  }
33  NOTREACHED();
34  return quota::kStorageTypeUnknown;
35}
36
37int WriteAdapter(PlatformFile file, int64 offset,
38                 scoped_ptr<char[]> data, int size) {
39  return base::WritePlatformFile(file, offset, data.get(), size);
40}
41
42}  // namespace
43
44class QuotaFileIO::PendingOperationBase {
45 public:
46  virtual ~PendingOperationBase() {}
47
48  // Either one of Run() or DidFail() is called (the latter is called when
49  // there was more than one error during quota queries).
50  virtual void Run() = 0;
51  virtual void DidFail(PlatformFileError error) = 0;
52
53 protected:
54  PendingOperationBase(QuotaFileIO* quota_io, bool is_will_operation)
55      : quota_io_(quota_io), is_will_operation_(is_will_operation) {
56    DCHECK(quota_io_);
57    quota_io_->WillUpdate();
58  }
59
60  QuotaFileIO* quota_io_;
61  const bool is_will_operation_;
62};
63
64class QuotaFileIO::WriteOperation : public PendingOperationBase {
65 public:
66  WriteOperation(QuotaFileIO* quota_io,
67                 bool is_will_operation,
68                 int64_t offset,
69                 const char* buffer,
70                 int32_t bytes_to_write,
71                 const WriteCallback& callback)
72      : PendingOperationBase(quota_io, is_will_operation),
73        offset_(offset),
74        bytes_to_write_(bytes_to_write),
75        callback_(callback),
76        finished_(false),
77        status_(base::PLATFORM_FILE_OK),
78        bytes_written_(0),
79        weak_factory_(this) {
80    if (!is_will_operation) {
81      // TODO(kinuko): Check the API convention if we really need to keep a copy
82      // of the buffer during the async write operations.
83      buffer_.reset(new char[bytes_to_write]);
84      memcpy(buffer_.get(), buffer, bytes_to_write);
85    }
86  }
87  virtual ~WriteOperation() {}
88  virtual void Run() OVERRIDE {
89    DCHECK(quota_io_);
90    if (quota_io_->CheckIfExceedsQuota(offset_ + bytes_to_write_)) {
91      DidFail(base::PLATFORM_FILE_ERROR_NO_SPACE);
92      return;
93    }
94    if (is_will_operation_) {
95      // Assuming the write will succeed.
96      DidFinish(base::PLATFORM_FILE_OK, bytes_to_write_);
97      return;
98    }
99    DCHECK(buffer_.get());
100
101    if (!base::PostTaskAndReplyWithResult(
102            quota_io_->delegate()->GetFileThreadMessageLoopProxy().get(),
103            FROM_HERE,
104            base::Bind(&WriteAdapter,
105                       quota_io_->file_,
106                       offset_,
107                       base::Passed(&buffer_),
108                       bytes_to_write_),
109            base::Bind(&WriteOperation::DidWrite,
110                       weak_factory_.GetWeakPtr()))) {
111      DidFail(base::PLATFORM_FILE_ERROR_FAILED);
112      return;
113    }
114  }
115
116  virtual void DidFail(PlatformFileError error) OVERRIDE {
117    DidFinish(error, 0);
118  }
119
120  bool finished() const { return finished_; }
121
122  virtual void WillRunCallback() {
123    base::MessageLoopProxy::current()->PostTask(
124        FROM_HERE,
125        base::Bind(&WriteOperation::RunCallback, weak_factory_.GetWeakPtr()));
126  }
127
128 private:
129  void DidWrite(int bytes_written) {
130    base::PlatformFileError error = bytes_written > 0 ?
131        base::PLATFORM_FILE_OK : base::PLATFORM_FILE_ERROR_FAILED;
132    DidFinish(error, bytes_written);
133  }
134
135  void DidFinish(PlatformFileError status, int bytes_written) {
136    finished_ = true;
137    status_ = status;
138    bytes_written_ = bytes_written;
139    int64_t max_offset =
140        (status != base::PLATFORM_FILE_OK) ? 0 : offset_ + bytes_written;
141    // This may delete itself by calling RunCallback.
142    quota_io_->DidWrite(this, max_offset);
143  }
144
145  virtual void RunCallback() {
146    DCHECK_EQ(false, callback_.is_null());
147    callback_.Run(status_, bytes_written_);
148    delete this;
149  }
150
151  const int64_t offset_;
152  scoped_ptr<char[]> buffer_;
153  const int32_t bytes_to_write_;
154  WriteCallback callback_;
155  bool finished_;
156  PlatformFileError status_;
157  int64_t bytes_written_;
158  base::WeakPtrFactory<WriteOperation> weak_factory_;
159};
160
161class QuotaFileIO::SetLengthOperation : public PendingOperationBase {
162 public:
163  SetLengthOperation(QuotaFileIO* quota_io,
164                     bool is_will_operation,
165                     int64_t length,
166                     const StatusCallback& callback)
167      : PendingOperationBase(quota_io, is_will_operation),
168        length_(length),
169        callback_(callback),
170        weak_factory_(this) {}
171
172  virtual ~SetLengthOperation() {}
173
174  virtual void Run() OVERRIDE {
175    DCHECK(quota_io_);
176    if (quota_io_->CheckIfExceedsQuota(length_)) {
177      DidFail(base::PLATFORM_FILE_ERROR_NO_SPACE);
178      return;
179    }
180    if (is_will_operation_) {
181      DidFinish(base::PLATFORM_FILE_OK);
182      return;
183    }
184
185    if (!base::FileUtilProxy::Truncate(
186            quota_io_->delegate()->GetFileThreadMessageLoopProxy().get(),
187            quota_io_->file_,
188            length_,
189            base::Bind(&SetLengthOperation::DidFinish,
190                       weak_factory_.GetWeakPtr()))) {
191      DidFail(base::PLATFORM_FILE_ERROR_FAILED);
192      return;
193    }
194  }
195
196  virtual void DidFail(PlatformFileError error) OVERRIDE {
197    DidFinish(error);
198  }
199
200 private:
201  void DidFinish(PlatformFileError status) {
202    quota_io_->DidSetLength(status, length_);
203    DCHECK_EQ(false, callback_.is_null());
204    callback_.Run(status);
205    delete this;
206  }
207
208  int64_t length_;
209  StatusCallback callback_;
210  base::WeakPtrFactory<SetLengthOperation> weak_factory_;
211};
212
213// QuotaFileIO --------------------------------------------------------------
214
215QuotaFileIO::QuotaFileIO(
216    Delegate* delegate,
217    PlatformFile file,
218    const GURL& file_url,
219    PP_FileSystemType type)
220    : delegate_(delegate),
221      file_(file),
222      file_url_(file_url),
223      storage_type_(PPFileSystemTypeToQuotaStorageType(type)),
224      cached_file_size_(0),
225      cached_available_space_(0),
226      outstanding_quota_queries_(0),
227      outstanding_errors_(0),
228      max_written_offset_(0),
229      inflight_operations_(0),
230      weak_factory_(this) {
231  DCHECK_NE(base::kInvalidPlatformFileValue, file_);
232  DCHECK_NE(quota::kStorageTypeUnknown, storage_type_);
233}
234
235QuotaFileIO::~QuotaFileIO() {
236  // Note that this doesn't dispatch pending callbacks.
237  STLDeleteContainerPointers(pending_operations_.begin(),
238                             pending_operations_.end());
239  STLDeleteContainerPointers(pending_callbacks_.begin(),
240                             pending_callbacks_.end());
241}
242
243bool QuotaFileIO::Write(
244    int64_t offset, const char* buffer, int32_t bytes_to_write,
245    const WriteCallback& callback) {
246  if (bytes_to_write <= 0)
247    return false;
248
249  WriteOperation* op = new WriteOperation(
250      this, false, offset, buffer, bytes_to_write, callback);
251  return RegisterOperationForQuotaChecks(op);
252}
253
254bool QuotaFileIO::SetLength(int64_t length, const StatusCallback& callback) {
255  DCHECK(pending_operations_.empty());
256  SetLengthOperation* op = new SetLengthOperation(
257      this, false, length, callback);
258  return RegisterOperationForQuotaChecks(op);
259}
260
261bool QuotaFileIO::WillWrite(
262    int64_t offset, int32_t bytes_to_write, const WriteCallback& callback) {
263  WriteOperation* op = new WriteOperation(
264      this, true, offset, NULL, bytes_to_write, callback);
265  return RegisterOperationForQuotaChecks(op);
266}
267
268bool QuotaFileIO::WillSetLength(int64_t length,
269                                const StatusCallback& callback) {
270  DCHECK(pending_operations_.empty());
271  SetLengthOperation* op = new SetLengthOperation(this, true, length, callback);
272  return RegisterOperationForQuotaChecks(op);
273}
274
275bool QuotaFileIO::RegisterOperationForQuotaChecks(
276    PendingOperationBase* op_ptr) {
277  scoped_ptr<PendingOperationBase> op(op_ptr);
278  if (pending_operations_.empty()) {
279    // This is the first pending quota check. Run querying the file size
280    // and available space.
281    outstanding_quota_queries_ = 0;
282    outstanding_errors_ = 0;
283
284    // Query the file size.
285    ++outstanding_quota_queries_;
286    if (!base::FileUtilProxy::GetFileInfoFromPlatformFile(
287            delegate_->GetFileThreadMessageLoopProxy().get(),
288            file_,
289            base::Bind(&QuotaFileIO::DidQueryInfoForQuota,
290                       weak_factory_.GetWeakPtr()))) {
291      // This makes the call fail synchronously; we do not fire the callback
292      // here but just delete the operation and return false.
293      return false;
294    }
295
296    // Query the current available space.
297    ++outstanding_quota_queries_;
298    delegate_->QueryAvailableSpace(
299        file_url_.GetOrigin(), storage_type_,
300        base::Bind(&QuotaFileIO::DidQueryAvailableSpace,
301                   weak_factory_.GetWeakPtr()));
302  }
303  pending_operations_.push_back(op.release());
304  return true;
305}
306
307void QuotaFileIO::DidQueryInfoForQuota(
308    base::PlatformFileError error_code,
309    const base::PlatformFileInfo& file_info) {
310  if (error_code != base::PLATFORM_FILE_OK)
311    ++outstanding_errors_;
312  cached_file_size_ = file_info.size;
313  DCHECK_GT(outstanding_quota_queries_, 0);
314  if (--outstanding_quota_queries_ == 0)
315    DidQueryForQuotaCheck();
316}
317
318void QuotaFileIO::DidQueryAvailableSpace(int64_t avail_space) {
319  cached_available_space_ = avail_space;
320  DCHECK_GT(outstanding_quota_queries_, 0);
321  if (--outstanding_quota_queries_ == 0)
322    DidQueryForQuotaCheck();
323}
324
325void QuotaFileIO::DidQueryForQuotaCheck() {
326  DCHECK(!pending_operations_.empty());
327  DCHECK_GT(inflight_operations_, 0);
328  while (!pending_operations_.empty()) {
329    PendingOperationBase* op = pending_operations_.front();
330    pending_operations_.pop_front();
331    pending_callbacks_.push_back(op);
332    if (outstanding_errors_ > 0) {
333      op->DidFail(base::PLATFORM_FILE_ERROR_FAILED);
334      continue;
335    }
336    op->Run();
337  }
338}
339
340bool QuotaFileIO::CheckIfExceedsQuota(int64_t new_file_size) const {
341  DCHECK_GE(cached_file_size_, 0);
342  DCHECK_GE(cached_available_space_, 0);
343  return new_file_size - cached_file_size_ > cached_available_space_;
344}
345
346void QuotaFileIO::WillUpdate() {
347  if (inflight_operations_++ == 0) {
348    delegate_->WillUpdateFile(file_url_);
349    DCHECK_EQ(0, max_written_offset_);
350  }
351}
352
353void QuotaFileIO::DidWrite(WriteOperation* op,
354                           int64_t written_offset_end) {
355  max_written_offset_ = std::max(max_written_offset_, written_offset_end);
356  DCHECK_GT(inflight_operations_, 0);
357  DCHECK(!pending_callbacks_.empty());
358  // Fire callbacks for finished operations.
359  while (!pending_callbacks_.empty()) {
360    WriteOperation* op = static_cast<WriteOperation*>(
361        pending_callbacks_.front());
362    if (!op->finished())
363      break;
364    pending_callbacks_.pop_front();
365    op->WillRunCallback();
366  }
367  // If we have no more pending writes, notify the browser that we did
368  // update the file.
369  if (--inflight_operations_ == 0) {
370    DCHECK(pending_operations_.empty());
371    int64_t growth = max_written_offset_ - cached_file_size_;
372    growth = growth < 0 ? 0 : growth;
373
374    delegate_->DidUpdateFile(file_url_, growth);
375    max_written_offset_ = 0;
376  }
377}
378
379void QuotaFileIO::DidSetLength(PlatformFileError error, int64_t new_file_size) {
380  DCHECK_EQ(1, inflight_operations_);
381  pending_callbacks_.pop_front();
382  DCHECK(pending_callbacks_.empty());
383  int64_t delta = (error != base::PLATFORM_FILE_OK) ? 0 :
384      new_file_size - cached_file_size_;
385
386  delegate_->DidUpdateFile(file_url_, delta);
387  inflight_operations_ = 0;
388}
389
390}  // namespace content
391