Home | History | Annotate | Download | only in filesystem
      1 /*
      2  * Copyright (C) 2010 Google Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "modules/filesystem/FileWriter.h"
     33 
     34 #include "bindings/v8/ExceptionState.h"
     35 #include "core/dom/ExceptionCode.h"
     36 #include "core/dom/ProgressEvent.h"
     37 #include "core/fileapi/Blob.h"
     38 #include "core/fileapi/FileError.h"
     39 #include "modules/filesystem/AsyncFileWriter.h"
     40 #include "wtf/CurrentTime.h"
     41 
     42 namespace WebCore {
     43 
     44 static const int kMaxRecursionDepth = 3;
     45 static const double progressNotificationIntervalMS = 50;
     46 
     47 PassRefPtr<FileWriter> FileWriter::create(ScriptExecutionContext* context)
     48 {
     49     RefPtr<FileWriter> fileWriter(adoptRef(new FileWriter(context)));
     50     fileWriter->suspendIfNeeded();
     51     return fileWriter.release();
     52 }
     53 
     54 FileWriter::FileWriter(ScriptExecutionContext* context)
     55     : ActiveDOMObject(context)
     56     , m_readyState(INIT)
     57     , m_operationInProgress(OperationNone)
     58     , m_queuedOperation(OperationNone)
     59     , m_bytesWritten(0)
     60     , m_bytesToWrite(0)
     61     , m_truncateLength(-1)
     62     , m_numAborts(0)
     63     , m_recursionDepth(0)
     64     , m_lastProgressNotificationTimeMS(0)
     65 {
     66     ScriptWrappable::init(this);
     67 }
     68 
     69 FileWriter::~FileWriter()
     70 {
     71     ASSERT(!m_recursionDepth);
     72     if (m_readyState == WRITING)
     73         stop();
     74 }
     75 
     76 const AtomicString& FileWriter::interfaceName() const
     77 {
     78     return eventNames().interfaceForFileWriter;
     79 }
     80 
     81 bool FileWriter::canSuspend() const
     82 {
     83     // FIXME: It is not currently possible to suspend a FileWriter, so pages with FileWriter can not go into page cache.
     84     return false;
     85 }
     86 
     87 void FileWriter::stop()
     88 {
     89     // Make sure we've actually got something to stop, and haven't already called abort().
     90     if (!writer() || m_readyState != WRITING)
     91         return;
     92     doOperation(OperationAbort);
     93     m_readyState = DONE;
     94 }
     95 
     96 void FileWriter::write(Blob* data, ExceptionState& es)
     97 {
     98     ASSERT(writer());
     99     ASSERT(data);
    100     ASSERT(m_truncateLength == -1);
    101     if (m_readyState == WRITING) {
    102         setError(FileError::INVALID_STATE_ERR, es);
    103         return;
    104     }
    105     if (!data) {
    106         setError(FileError::TYPE_MISMATCH_ERR, es);
    107         return;
    108     }
    109     if (m_recursionDepth > kMaxRecursionDepth) {
    110         setError(FileError::SECURITY_ERR, es);
    111         return;
    112     }
    113 
    114     m_blobBeingWritten = data;
    115     m_readyState = WRITING;
    116     m_bytesWritten = 0;
    117     m_bytesToWrite = data->size();
    118     ASSERT(m_queuedOperation == OperationNone);
    119     if (m_operationInProgress != OperationNone) {
    120         // We must be waiting for an abort to complete, since m_readyState wasn't WRITING.
    121         ASSERT(m_operationInProgress == OperationAbort);
    122         m_queuedOperation = OperationWrite;
    123     } else
    124         doOperation(OperationWrite);
    125 
    126     fireEvent(eventNames().writestartEvent);
    127 }
    128 
    129 void FileWriter::seek(long long position, ExceptionState& es)
    130 {
    131     ASSERT(writer());
    132     if (m_readyState == WRITING) {
    133         setError(FileError::INVALID_STATE_ERR, es);
    134         return;
    135     }
    136 
    137     ASSERT(m_truncateLength == -1);
    138     ASSERT(m_queuedOperation == OperationNone);
    139     seekInternal(position);
    140 }
    141 
    142 void FileWriter::truncate(long long position, ExceptionState& es)
    143 {
    144     ASSERT(writer());
    145     ASSERT(m_truncateLength == -1);
    146     if (m_readyState == WRITING || position < 0) {
    147         setError(FileError::INVALID_STATE_ERR, es);
    148         return;
    149     }
    150     if (m_recursionDepth > kMaxRecursionDepth) {
    151         setError(FileError::SECURITY_ERR, es);
    152         return;
    153     }
    154 
    155     m_readyState = WRITING;
    156     m_bytesWritten = 0;
    157     m_bytesToWrite = 0;
    158     m_truncateLength = position;
    159     ASSERT(m_queuedOperation == OperationNone);
    160     if (m_operationInProgress != OperationNone) {
    161         // We must be waiting for an abort to complete, since m_readyState wasn't WRITING.
    162         ASSERT(m_operationInProgress == OperationAbort);
    163         m_queuedOperation = OperationTruncate;
    164     } else
    165         doOperation(OperationTruncate);
    166     fireEvent(eventNames().writestartEvent);
    167 }
    168 
    169 void FileWriter::abort(ExceptionState& es)
    170 {
    171     ASSERT(writer());
    172     if (m_readyState != WRITING)
    173         return;
    174     ++m_numAborts;
    175 
    176     doOperation(OperationAbort);
    177     signalCompletion(FileError::ABORT_ERR);
    178 }
    179 
    180 void FileWriter::didWrite(long long bytes, bool complete)
    181 {
    182     if (m_operationInProgress == OperationAbort) {
    183         completeAbort();
    184         return;
    185     }
    186     ASSERT(m_readyState == WRITING);
    187     ASSERT(m_truncateLength == -1);
    188     ASSERT(m_operationInProgress == OperationWrite);
    189     ASSERT(!m_bytesToWrite || bytes + m_bytesWritten > 0);
    190     ASSERT(bytes + m_bytesWritten <= m_bytesToWrite);
    191     m_bytesWritten += bytes;
    192     ASSERT((m_bytesWritten == m_bytesToWrite) || !complete);
    193     setPosition(position() + bytes);
    194     if (position() > length())
    195         setLength(position());
    196     if (complete) {
    197         m_blobBeingWritten.clear();
    198         m_operationInProgress = OperationNone;
    199     }
    200 
    201     int numAborts = m_numAborts;
    202     // We could get an abort in the handler for this event. If we do, it's
    203     // already handled the cleanup and signalCompletion call.
    204     double now = currentTimeMS();
    205     if (complete || !m_lastProgressNotificationTimeMS || (now - m_lastProgressNotificationTimeMS > progressNotificationIntervalMS)) {
    206         m_lastProgressNotificationTimeMS = now;
    207         fireEvent(eventNames().progressEvent);
    208     }
    209 
    210     if (complete) {
    211       if (numAborts == m_numAborts)
    212           signalCompletion(FileError::OK);
    213       unsetPendingActivity(this);
    214     }
    215 }
    216 
    217 void FileWriter::didTruncate()
    218 {
    219     if (m_operationInProgress == OperationAbort) {
    220         completeAbort();
    221         return;
    222     }
    223     ASSERT(m_operationInProgress == OperationTruncate);
    224     ASSERT(m_truncateLength >= 0);
    225     setLength(m_truncateLength);
    226     if (position() > length())
    227         setPosition(length());
    228     m_operationInProgress = OperationNone;
    229     signalCompletion(FileError::OK);
    230     unsetPendingActivity(this);
    231 }
    232 
    233 void FileWriter::didFail(FileError::ErrorCode code)
    234 {
    235     ASSERT(m_operationInProgress != OperationNone);
    236     ASSERT(code != FileError::OK);
    237     if (m_operationInProgress == OperationAbort) {
    238         completeAbort();
    239         return;
    240     }
    241     ASSERT(code != FileError::ABORT_ERR);
    242     ASSERT(m_queuedOperation == OperationNone);
    243     ASSERT(m_readyState == WRITING);
    244     m_blobBeingWritten.clear();
    245     m_operationInProgress = OperationNone;
    246     signalCompletion(code);
    247     unsetPendingActivity(this);
    248 }
    249 
    250 void FileWriter::completeAbort()
    251 {
    252     ASSERT(m_operationInProgress == OperationAbort);
    253     m_operationInProgress = OperationNone;
    254     Operation operation = m_queuedOperation;
    255     m_queuedOperation = OperationNone;
    256     doOperation(operation);
    257     unsetPendingActivity(this);
    258 }
    259 
    260 void FileWriter::doOperation(Operation operation)
    261 {
    262     switch (operation) {
    263     case OperationWrite:
    264         ASSERT(m_operationInProgress == OperationNone);
    265         ASSERT(m_truncateLength == -1);
    266         ASSERT(m_blobBeingWritten.get());
    267         ASSERT(m_readyState == WRITING);
    268         setPendingActivity(this);
    269         writer()->write(position(), m_blobBeingWritten.get());
    270         break;
    271     case OperationTruncate:
    272         ASSERT(m_operationInProgress == OperationNone);
    273         ASSERT(m_truncateLength >= 0);
    274         ASSERT(m_readyState == WRITING);
    275         setPendingActivity(this);
    276         writer()->truncate(m_truncateLength);
    277         break;
    278     case OperationNone:
    279         ASSERT(m_operationInProgress == OperationNone);
    280         ASSERT(m_truncateLength == -1);
    281         ASSERT(!m_blobBeingWritten.get());
    282         ASSERT(m_readyState == DONE);
    283         break;
    284     case OperationAbort:
    285         if (m_operationInProgress == OperationWrite || m_operationInProgress == OperationTruncate)
    286             writer()->abort();
    287         else if (m_operationInProgress != OperationAbort)
    288             operation = OperationNone;
    289         m_queuedOperation = OperationNone;
    290         m_blobBeingWritten.clear();
    291         m_truncateLength = -1;
    292         break;
    293     }
    294     ASSERT(m_queuedOperation == OperationNone);
    295     m_operationInProgress = operation;
    296 }
    297 
    298 void FileWriter::signalCompletion(FileError::ErrorCode code)
    299 {
    300     m_readyState = DONE;
    301     m_truncateLength = -1;
    302     if (FileError::OK != code) {
    303         m_error = FileError::create(code);
    304         if (FileError::ABORT_ERR == code)
    305             fireEvent(eventNames().abortEvent);
    306         else
    307             fireEvent(eventNames().errorEvent);
    308     } else
    309         fireEvent(eventNames().writeEvent);
    310     fireEvent(eventNames().writeendEvent);
    311 }
    312 
    313 void FileWriter::fireEvent(const AtomicString& type)
    314 {
    315     ++m_recursionDepth;
    316     dispatchEvent(ProgressEvent::create(type, true, m_bytesWritten, m_bytesToWrite));
    317     --m_recursionDepth;
    318     ASSERT(m_recursionDepth >= 0);
    319 }
    320 
    321 void FileWriter::setError(FileError::ErrorCode errorCode, ExceptionState& es)
    322 {
    323     ASSERT(errorCode);
    324     FileError::throwDOMException(es, errorCode);
    325     m_error = FileError::create(errorCode);
    326 }
    327 
    328 } // namespace WebCore
    329