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 "webkit/browser/fileapi/file_writer_delegate.h"
6
7#include "base/bind.h"
8#include "base/callback.h"
9#include "base/files/file_util_proxy.h"
10#include "base/message_loop/message_loop.h"
11#include "base/message_loop/message_loop_proxy.h"
12#include "base/sequenced_task_runner.h"
13#include "base/threading/thread_restrictions.h"
14#include "net/base/net_errors.h"
15#include "webkit/browser/fileapi/file_stream_writer.h"
16#include "webkit/browser/fileapi/file_system_context.h"
17#include "webkit/common/fileapi/file_system_util.h"
18
19namespace fileapi {
20
21static const int kReadBufSize = 32768;
22
23FileWriterDelegate::FileWriterDelegate(
24    scoped_ptr<FileStreamWriter> file_stream_writer)
25    : file_stream_writer_(file_stream_writer.Pass()),
26      writing_started_(false),
27      bytes_written_backlog_(0),
28      bytes_written_(0),
29      bytes_read_(0),
30      io_buffer_(new net::IOBufferWithSize(kReadBufSize)),
31      weak_factory_(this) {
32}
33
34FileWriterDelegate::~FileWriterDelegate() {
35}
36
37void FileWriterDelegate::Start(scoped_ptr<net::URLRequest> request,
38                               const DelegateWriteCallback& write_callback) {
39  write_callback_ = write_callback;
40  request_ = request.Pass();
41  request_->Start();
42}
43
44void FileWriterDelegate::Cancel() {
45  if (request_) {
46    // This halts any callbacks on this delegate.
47    request_->set_delegate(NULL);
48    request_->Cancel();
49  }
50
51  const int status = file_stream_writer_->Cancel(
52      base::Bind(&FileWriterDelegate::OnWriteCancelled,
53                 weak_factory_.GetWeakPtr()));
54  // Return true to finish immediately if we have no pending writes.
55  // Otherwise we'll do the final cleanup in the Cancel callback.
56  if (status != net::ERR_IO_PENDING) {
57    write_callback_.Run(base::PLATFORM_FILE_ERROR_ABORT, 0,
58                        GetCompletionStatusOnError());
59  }
60}
61
62void FileWriterDelegate::OnReceivedRedirect(net::URLRequest* request,
63                                            const GURL& new_url,
64                                            bool* defer_redirect) {
65  NOTREACHED();
66  OnError(base::PLATFORM_FILE_ERROR_SECURITY);
67}
68
69void FileWriterDelegate::OnAuthRequired(net::URLRequest* request,
70                                        net::AuthChallengeInfo* auth_info) {
71  NOTREACHED();
72  OnError(base::PLATFORM_FILE_ERROR_SECURITY);
73}
74
75void FileWriterDelegate::OnCertificateRequested(
76    net::URLRequest* request,
77    net::SSLCertRequestInfo* cert_request_info) {
78  NOTREACHED();
79  OnError(base::PLATFORM_FILE_ERROR_SECURITY);
80}
81
82void FileWriterDelegate::OnSSLCertificateError(net::URLRequest* request,
83                                               const net::SSLInfo& ssl_info,
84                                               bool fatal) {
85  NOTREACHED();
86  OnError(base::PLATFORM_FILE_ERROR_SECURITY);
87}
88
89void FileWriterDelegate::OnResponseStarted(net::URLRequest* request) {
90  DCHECK_EQ(request_.get(), request);
91  if (!request->status().is_success() || request->GetResponseCode() != 200) {
92    OnError(base::PLATFORM_FILE_ERROR_FAILED);
93    return;
94  }
95  Read();
96}
97
98void FileWriterDelegate::OnReadCompleted(net::URLRequest* request,
99                                         int bytes_read) {
100  DCHECK_EQ(request_.get(), request);
101  if (!request->status().is_success()) {
102    OnError(base::PLATFORM_FILE_ERROR_FAILED);
103    return;
104  }
105  OnDataReceived(bytes_read);
106}
107
108void FileWriterDelegate::Read() {
109  bytes_written_ = 0;
110  bytes_read_ = 0;
111  if (request_->Read(io_buffer_.get(), io_buffer_->size(), &bytes_read_)) {
112    base::MessageLoop::current()->PostTask(
113        FROM_HERE,
114        base::Bind(&FileWriterDelegate::OnDataReceived,
115                   weak_factory_.GetWeakPtr(), bytes_read_));
116  } else if (!request_->status().is_io_pending()) {
117    OnError(base::PLATFORM_FILE_ERROR_FAILED);
118  }
119}
120
121void FileWriterDelegate::OnDataReceived(int bytes_read) {
122  bytes_read_ = bytes_read;
123  if (!bytes_read_) {  // We're done.
124    OnProgress(0, true);
125  } else {
126    // This could easily be optimized to rotate between a pool of buffers, so
127    // that we could read and write at the same time.  It's not yet clear that
128    // it's necessary.
129    cursor_ = new net::DrainableIOBuffer(io_buffer_.get(), bytes_read_);
130    Write();
131  }
132}
133
134void FileWriterDelegate::Write() {
135  writing_started_ = true;
136  int64 bytes_to_write = bytes_read_ - bytes_written_;
137  int write_response =
138      file_stream_writer_->Write(cursor_.get(),
139                                 static_cast<int>(bytes_to_write),
140                                 base::Bind(&FileWriterDelegate::OnDataWritten,
141                                            weak_factory_.GetWeakPtr()));
142  if (write_response > 0) {
143    base::MessageLoop::current()->PostTask(
144        FROM_HERE,
145        base::Bind(&FileWriterDelegate::OnDataWritten,
146                   weak_factory_.GetWeakPtr(), write_response));
147  } else if (net::ERR_IO_PENDING != write_response) {
148    OnError(NetErrorToPlatformFileError(write_response));
149  }
150}
151
152void FileWriterDelegate::OnDataWritten(int write_response) {
153  if (write_response > 0) {
154    OnProgress(write_response, false);
155    cursor_->DidConsume(write_response);
156    bytes_written_ += write_response;
157    if (bytes_written_ == bytes_read_)
158      Read();
159    else
160      Write();
161  } else {
162    OnError(NetErrorToPlatformFileError(write_response));
163  }
164}
165
166FileWriterDelegate::WriteProgressStatus
167FileWriterDelegate::GetCompletionStatusOnError() const {
168  return writing_started_ ? ERROR_WRITE_STARTED : ERROR_WRITE_NOT_STARTED;
169}
170
171void FileWriterDelegate::OnError(base::PlatformFileError error) {
172  if (request_) {
173    request_->set_delegate(NULL);
174    request_->Cancel();
175  }
176
177  if (writing_started_)
178    FlushForCompletion(error, 0, ERROR_WRITE_STARTED);
179  else
180    write_callback_.Run(error, 0, ERROR_WRITE_NOT_STARTED);
181}
182
183void FileWriterDelegate::OnProgress(int bytes_written, bool done) {
184  DCHECK(bytes_written + bytes_written_backlog_ >= bytes_written_backlog_);
185  static const int kMinProgressDelayMS = 200;
186  base::Time currentTime = base::Time::Now();
187  if (done || last_progress_event_time_.is_null() ||
188      (currentTime - last_progress_event_time_).InMilliseconds() >
189          kMinProgressDelayMS) {
190    bytes_written += bytes_written_backlog_;
191    last_progress_event_time_ = currentTime;
192    bytes_written_backlog_ = 0;
193
194    if (done) {
195      FlushForCompletion(base::PLATFORM_FILE_OK, bytes_written,
196                         SUCCESS_COMPLETED);
197    } else {
198      write_callback_.Run(base::PLATFORM_FILE_OK, bytes_written,
199                          SUCCESS_IO_PENDING);
200    }
201    return;
202  }
203  bytes_written_backlog_ += bytes_written;
204}
205
206void FileWriterDelegate::OnWriteCancelled(int status) {
207  write_callback_.Run(base::PLATFORM_FILE_ERROR_ABORT, 0,
208                      GetCompletionStatusOnError());
209}
210
211void FileWriterDelegate::FlushForCompletion(
212    base::PlatformFileError error,
213    int bytes_written,
214    WriteProgressStatus progress_status) {
215  int flush_error = file_stream_writer_->Flush(
216      base::Bind(&FileWriterDelegate::OnFlushed, weak_factory_.GetWeakPtr(),
217                 error, bytes_written, progress_status));
218  if (flush_error != net::ERR_IO_PENDING)
219    OnFlushed(error, bytes_written, progress_status, flush_error);
220}
221
222void FileWriterDelegate::OnFlushed(base::PlatformFileError error,
223                                   int bytes_written,
224                                   WriteProgressStatus progress_status,
225                                   int flush_error) {
226  if (error == base::PLATFORM_FILE_OK && flush_error != net::OK) {
227    // If the Flush introduced an error, overwrite the status.
228    // Otherwise, keep the original error status.
229    error = NetErrorToPlatformFileError(flush_error);
230    progress_status = GetCompletionStatusOnError();
231  }
232  write_callback_.Run(error, bytes_written, progress_status);
233}
234
235}  // namespace fileapi
236