1// Copyright 2013 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/child/fileapi/webfilewriter_base.h"
6
7#include "base/logging.h"
8#include "storage/common/fileapi/file_system_util.h"
9#include "third_party/WebKit/public/platform/WebFileError.h"
10#include "third_party/WebKit/public/platform/WebFileWriterClient.h"
11#include "third_party/WebKit/public/platform/WebURL.h"
12
13using storage::FileErrorToWebFileError;
14
15namespace content {
16
17WebFileWriterBase::WebFileWriterBase(const GURL& path,
18                                     blink::WebFileWriterClient* client)
19    : path_(path),
20      client_(client),
21      operation_(kOperationNone),
22      cancel_state_(kCancelNotInProgress) {}
23
24WebFileWriterBase::~WebFileWriterBase() {}
25
26void WebFileWriterBase::truncate(long long length) {
27  DCHECK(kOperationNone == operation_);
28  DCHECK(kCancelNotInProgress == cancel_state_);
29  operation_ = kOperationTruncate;
30  DoTruncate(path_, length);
31}
32
33void WebFileWriterBase::write(
34      long long position,
35      const blink::WebString& id) {
36  DCHECK_EQ(kOperationNone, operation_);
37  DCHECK_EQ(kCancelNotInProgress, cancel_state_);
38  operation_ = kOperationWrite;
39  DoWrite(path_, id.utf8(), position);
40}
41
42// When we cancel a write/truncate, we always get back the result of the write
43// before the result of the cancel, no matter what happens.
44// So we'll get back either
45//   success [of the write/truncate, in a DidWrite(XXX, true)/DidSucceed() call]
46//     followed by failure [of the cancel]; or
47//   failure [of the write, either from cancel or other reasons] followed by
48//     the result of the cancel.
49// In the write case, there could also be queued up non-terminal DidWrite calls
50// before any of that comes back, but there will always be a terminal write
51// response [success or failure] after them, followed by the cancel result, so
52// we can ignore non-terminal write responses, take the terminal write success
53// or the first failure as the last write response, then know that the next
54// thing to come back is the cancel response.  We only notify the
55// AsyncFileWriterClient when it's all over.
56void WebFileWriterBase::cancel() {
57  // Check for the cancel passing the previous operation's return in-flight.
58  if (kOperationWrite != operation_ && kOperationTruncate != operation_)
59    return;
60  if (kCancelNotInProgress != cancel_state_)
61    return;
62  cancel_state_ = kCancelSent;
63  DoCancel();
64}
65
66void WebFileWriterBase::DidFinish(base::File::Error error_code) {
67  if (error_code == base::File::FILE_OK)
68    DidSucceed();
69  else
70    DidFail(error_code);
71}
72
73void WebFileWriterBase::DidWrite(int64 bytes, bool complete) {
74  DCHECK(kOperationWrite == operation_);
75  switch (cancel_state_) {
76    case kCancelNotInProgress:
77      if (complete)
78        operation_ = kOperationNone;
79      client_->didWrite(bytes, complete);
80      break;
81    case kCancelSent:
82      // This is the success call of the write, which we'll eat, even though
83      // it succeeded before the cancel got there.  We accepted the cancel call,
84      // so the write will eventually return an error.
85      if (complete)
86        cancel_state_ = kCancelReceivedWriteResponse;
87      break;
88    case kCancelReceivedWriteResponse:
89    default:
90      NOTREACHED();
91  }
92}
93
94void WebFileWriterBase::DidSucceed() {
95  // Write never gets a DidSucceed call, so this is either a cancel or truncate
96  // response.
97  switch (cancel_state_) {
98    case kCancelNotInProgress:
99      // A truncate succeeded, with no complications.
100      DCHECK(kOperationTruncate == operation_);
101      operation_ = kOperationNone;
102      client_->didTruncate();
103      break;
104    case kCancelSent:
105      DCHECK(kOperationTruncate == operation_);
106      // This is the success call of the truncate, which we'll eat, even though
107      // it succeeded before the cancel got there.  We accepted the cancel call,
108      // so the truncate will eventually return an error.
109      cancel_state_ = kCancelReceivedWriteResponse;
110      break;
111    case kCancelReceivedWriteResponse:
112      // This is the success of the cancel operation.
113      FinishCancel();
114      break;
115    default:
116      NOTREACHED();
117  }
118}
119
120void WebFileWriterBase::DidFail(base::File::Error error_code) {
121  DCHECK(kOperationNone != operation_);
122  switch (cancel_state_) {
123    case kCancelNotInProgress:
124      // A write or truncate failed.
125      operation_ = kOperationNone;
126      client_->didFail(FileErrorToWebFileError(error_code));
127      break;
128    case kCancelSent:
129      // This is the failure of a write or truncate; the next message should be
130      // the result of the cancel.  We don't assume that it'll be a success, as
131      // the write/truncate could have failed for other reasons.
132      cancel_state_ = kCancelReceivedWriteResponse;
133      break;
134    case kCancelReceivedWriteResponse:
135      // The cancel reported failure, meaning that the write or truncate
136      // finished before the cancel got there.  But we suppressed the
137      // write/truncate's response, and will now report that it was cancelled.
138      FinishCancel();
139      break;
140    default:
141      NOTREACHED();
142  }
143}
144
145void WebFileWriterBase::FinishCancel() {
146  DCHECK(kCancelReceivedWriteResponse == cancel_state_);
147  DCHECK(kOperationNone != operation_);
148  cancel_state_ = kCancelNotInProgress;
149  operation_ = kOperationNone;
150  client_->didFail(blink::WebFileErrorAbort);
151}
152
153}  // namespace content
154