普通文本  |  241行  |  6.59 KB

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "webkit/browser/fileapi/recursive_operation_delegate.h"

#include "base/bind.h"
#include "webkit/browser/fileapi/file_system_context.h"
#include "webkit/browser/fileapi/file_system_operation_runner.h"

namespace fileapi {

namespace {
// Don't start too many inflight operations.
const int kMaxInflightOperations = 5;
}

RecursiveOperationDelegate::RecursiveOperationDelegate(
    FileSystemContext* file_system_context)
    : file_system_context_(file_system_context),
      inflight_operations_(0),
      canceled_(false) {
}

RecursiveOperationDelegate::~RecursiveOperationDelegate() {
}

void RecursiveOperationDelegate::Cancel() {
  canceled_ = true;
  OnCancel();
}

void RecursiveOperationDelegate::StartRecursiveOperation(
    const FileSystemURL& root,
    const StatusCallback& callback) {
  DCHECK(pending_directory_stack_.empty());
  DCHECK(pending_files_.empty());
  DCHECK_EQ(0, inflight_operations_);

  callback_ = callback;
  ++inflight_operations_;
  ProcessFile(
      root,
      base::Bind(&RecursiveOperationDelegate::DidTryProcessFile,
                 AsWeakPtr(), root));
}

FileSystemOperationRunner* RecursiveOperationDelegate::operation_runner() {
  return file_system_context_->operation_runner();
}

void RecursiveOperationDelegate::OnCancel() {
}

void RecursiveOperationDelegate::DidTryProcessFile(
    const FileSystemURL& root,
    base::PlatformFileError error) {
  DCHECK(pending_directory_stack_.empty());
  DCHECK(pending_files_.empty());
  DCHECK_EQ(1, inflight_operations_);

  --inflight_operations_;
  if (canceled_ || error != base::PLATFORM_FILE_ERROR_NOT_A_FILE) {
    Done(error);
    return;
  }

  pending_directory_stack_.push(std::queue<FileSystemURL>());
  pending_directory_stack_.top().push(root);
  ProcessNextDirectory();
}

void RecursiveOperationDelegate::ProcessNextDirectory() {
  DCHECK(pending_files_.empty());
  DCHECK(!pending_directory_stack_.empty());
  DCHECK(!pending_directory_stack_.top().empty());
  DCHECK_EQ(0, inflight_operations_);

  const FileSystemURL& url = pending_directory_stack_.top().front();

  ++inflight_operations_;
  ProcessDirectory(
      url,
      base::Bind(
          &RecursiveOperationDelegate::DidProcessDirectory, AsWeakPtr()));
}

void RecursiveOperationDelegate::DidProcessDirectory(
    base::PlatformFileError error) {
  DCHECK(pending_files_.empty());
  DCHECK(!pending_directory_stack_.empty());
  DCHECK(!pending_directory_stack_.top().empty());
  DCHECK_EQ(1, inflight_operations_);

  --inflight_operations_;
  if (canceled_ || error != base::PLATFORM_FILE_OK) {
    Done(error);
    return;
  }

  const FileSystemURL& parent = pending_directory_stack_.top().front();
  pending_directory_stack_.push(std::queue<FileSystemURL>());
  operation_runner()->ReadDirectory(
      parent,
      base::Bind(&RecursiveOperationDelegate::DidReadDirectory,
                 AsWeakPtr(), parent));
}

void RecursiveOperationDelegate::DidReadDirectory(
    const FileSystemURL& parent,
    base::PlatformFileError error,
    const FileEntryList& entries,
    bool has_more) {
  DCHECK(pending_files_.empty());
  DCHECK(!pending_directory_stack_.empty());
  DCHECK_EQ(0, inflight_operations_);

  if (canceled_ || error != base::PLATFORM_FILE_OK) {
    Done(error);
    return;
  }

  for (size_t i = 0; i < entries.size(); i++) {
    FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL(
        parent.origin(),
        parent.mount_type(),
        parent.virtual_path().Append(entries[i].name));
    if (entries[i].is_directory)
      pending_directory_stack_.top().push(url);
    else
      pending_files_.push(url);
  }

  // Wait for next entries.
  if (has_more)
    return;

  ProcessPendingFiles();
}

void RecursiveOperationDelegate::ProcessPendingFiles() {
  DCHECK(!pending_directory_stack_.empty());

  if ((pending_files_.empty() || canceled_) && inflight_operations_ == 0) {
    ProcessSubDirectory();
    return;
  }

  // Do not post any new tasks.
  if (canceled_)
    return;

  // Run ProcessFile in parallel (upto kMaxInflightOperations).
  scoped_refptr<base::MessageLoopProxy> current_message_loop =
      base::MessageLoopProxy::current();
  while (!pending_files_.empty() &&
         inflight_operations_ < kMaxInflightOperations) {
    ++inflight_operations_;
    current_message_loop->PostTask(
        FROM_HERE,
        base::Bind(&RecursiveOperationDelegate::ProcessFile,
                   AsWeakPtr(), pending_files_.front(),
                   base::Bind(&RecursiveOperationDelegate::DidProcessFile,
                              AsWeakPtr())));
    pending_files_.pop();
  }
}

void RecursiveOperationDelegate::DidProcessFile(
    base::PlatformFileError error) {
  --inflight_operations_;
  if (error != base::PLATFORM_FILE_OK) {
    // If an error occurs, invoke Done immediately (even if there remain
    // running operations). It is because in the callback, this instance is
    // deleted.
    Done(error);
    return;
  }

  ProcessPendingFiles();
}

void RecursiveOperationDelegate::ProcessSubDirectory() {
  DCHECK(pending_files_.empty());
  DCHECK(!pending_directory_stack_.empty());
  DCHECK_EQ(0, inflight_operations_);

  if (canceled_) {
    Done(base::PLATFORM_FILE_ERROR_ABORT);
    return;
  }

  if (!pending_directory_stack_.top().empty()) {
    // There remain some sub directories. Process them first.
    ProcessNextDirectory();
    return;
  }

  // All subdirectories are processed.
  pending_directory_stack_.pop();
  if (pending_directory_stack_.empty()) {
    // All files/directories are processed.
    Done(base::PLATFORM_FILE_OK);
    return;
  }

  DCHECK(!pending_directory_stack_.top().empty());
  ++inflight_operations_;
  PostProcessDirectory(
      pending_directory_stack_.top().front(),
      base::Bind(&RecursiveOperationDelegate::DidPostProcessDirectory,
                 AsWeakPtr()));
}

void RecursiveOperationDelegate::DidPostProcessDirectory(
    base::PlatformFileError error) {
  DCHECK(pending_files_.empty());
  DCHECK(!pending_directory_stack_.empty());
  DCHECK(!pending_directory_stack_.top().empty());
  DCHECK_EQ(1, inflight_operations_);

  --inflight_operations_;
  pending_directory_stack_.top().pop();
  if (canceled_ || error != base::PLATFORM_FILE_OK) {
    Done(error);
    return;
  }

  ProcessSubDirectory();
}

void RecursiveOperationDelegate::Done(base::PlatformFileError error) {
  if (canceled_ && error == base::PLATFORM_FILE_OK) {
    callback_.Run(base::PLATFORM_FILE_ERROR_ABORT);
  } else {
    callback_.Run(error);
  }
}

}  // namespace fileapi