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