1 // Copyright (c) 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 "webkit/browser/fileapi/recursive_operation_delegate.h" 6 7 #include "base/bind.h" 8 #include "webkit/browser/fileapi/file_system_context.h" 9 #include "webkit/browser/fileapi/file_system_operation_runner.h" 10 11 namespace fileapi { 12 13 namespace { 14 // Don't start too many inflight operations. 15 const int kMaxInflightOperations = 5; 16 } 17 18 RecursiveOperationDelegate::RecursiveOperationDelegate( 19 FileSystemContext* file_system_context) 20 : file_system_context_(file_system_context), 21 inflight_operations_(0), 22 canceled_(false) { 23 } 24 25 RecursiveOperationDelegate::~RecursiveOperationDelegate() { 26 } 27 28 void RecursiveOperationDelegate::Cancel() { 29 canceled_ = true; 30 OnCancel(); 31 } 32 33 void RecursiveOperationDelegate::StartRecursiveOperation( 34 const FileSystemURL& root, 35 const StatusCallback& callback) { 36 DCHECK(pending_directory_stack_.empty()); 37 DCHECK(pending_files_.empty()); 38 DCHECK_EQ(0, inflight_operations_); 39 40 callback_ = callback; 41 ++inflight_operations_; 42 ProcessFile( 43 root, 44 base::Bind(&RecursiveOperationDelegate::DidTryProcessFile, 45 AsWeakPtr(), root)); 46 } 47 48 FileSystemOperationRunner* RecursiveOperationDelegate::operation_runner() { 49 return file_system_context_->operation_runner(); 50 } 51 52 void RecursiveOperationDelegate::OnCancel() { 53 } 54 55 void RecursiveOperationDelegate::DidTryProcessFile( 56 const FileSystemURL& root, 57 base::PlatformFileError error) { 58 DCHECK(pending_directory_stack_.empty()); 59 DCHECK(pending_files_.empty()); 60 DCHECK_EQ(1, inflight_operations_); 61 62 --inflight_operations_; 63 if (canceled_ || error != base::PLATFORM_FILE_ERROR_NOT_A_FILE) { 64 Done(error); 65 return; 66 } 67 68 pending_directory_stack_.push(std::queue<FileSystemURL>()); 69 pending_directory_stack_.top().push(root); 70 ProcessNextDirectory(); 71 } 72 73 void RecursiveOperationDelegate::ProcessNextDirectory() { 74 DCHECK(pending_files_.empty()); 75 DCHECK(!pending_directory_stack_.empty()); 76 DCHECK(!pending_directory_stack_.top().empty()); 77 DCHECK_EQ(0, inflight_operations_); 78 79 const FileSystemURL& url = pending_directory_stack_.top().front(); 80 81 ++inflight_operations_; 82 ProcessDirectory( 83 url, 84 base::Bind( 85 &RecursiveOperationDelegate::DidProcessDirectory, AsWeakPtr())); 86 } 87 88 void RecursiveOperationDelegate::DidProcessDirectory( 89 base::PlatformFileError error) { 90 DCHECK(pending_files_.empty()); 91 DCHECK(!pending_directory_stack_.empty()); 92 DCHECK(!pending_directory_stack_.top().empty()); 93 DCHECK_EQ(1, inflight_operations_); 94 95 --inflight_operations_; 96 if (canceled_ || error != base::PLATFORM_FILE_OK) { 97 Done(error); 98 return; 99 } 100 101 const FileSystemURL& parent = pending_directory_stack_.top().front(); 102 pending_directory_stack_.push(std::queue<FileSystemURL>()); 103 operation_runner()->ReadDirectory( 104 parent, 105 base::Bind(&RecursiveOperationDelegate::DidReadDirectory, 106 AsWeakPtr(), parent)); 107 } 108 109 void RecursiveOperationDelegate::DidReadDirectory( 110 const FileSystemURL& parent, 111 base::PlatformFileError error, 112 const FileEntryList& entries, 113 bool has_more) { 114 DCHECK(pending_files_.empty()); 115 DCHECK(!pending_directory_stack_.empty()); 116 DCHECK_EQ(0, inflight_operations_); 117 118 if (canceled_ || error != base::PLATFORM_FILE_OK) { 119 Done(error); 120 return; 121 } 122 123 for (size_t i = 0; i < entries.size(); i++) { 124 FileSystemURL url = file_system_context_->CreateCrackedFileSystemURL( 125 parent.origin(), 126 parent.mount_type(), 127 parent.virtual_path().Append(entries[i].name)); 128 if (entries[i].is_directory) 129 pending_directory_stack_.top().push(url); 130 else 131 pending_files_.push(url); 132 } 133 134 // Wait for next entries. 135 if (has_more) 136 return; 137 138 ProcessPendingFiles(); 139 } 140 141 void RecursiveOperationDelegate::ProcessPendingFiles() { 142 DCHECK(!pending_directory_stack_.empty()); 143 144 if ((pending_files_.empty() || canceled_) && inflight_operations_ == 0) { 145 ProcessSubDirectory(); 146 return; 147 } 148 149 // Do not post any new tasks. 150 if (canceled_) 151 return; 152 153 // Run ProcessFile in parallel (upto kMaxInflightOperations). 154 scoped_refptr<base::MessageLoopProxy> current_message_loop = 155 base::MessageLoopProxy::current(); 156 while (!pending_files_.empty() && 157 inflight_operations_ < kMaxInflightOperations) { 158 ++inflight_operations_; 159 current_message_loop->PostTask( 160 FROM_HERE, 161 base::Bind(&RecursiveOperationDelegate::ProcessFile, 162 AsWeakPtr(), pending_files_.front(), 163 base::Bind(&RecursiveOperationDelegate::DidProcessFile, 164 AsWeakPtr()))); 165 pending_files_.pop(); 166 } 167 } 168 169 void RecursiveOperationDelegate::DidProcessFile( 170 base::PlatformFileError error) { 171 --inflight_operations_; 172 if (error != base::PLATFORM_FILE_OK) { 173 // If an error occurs, invoke Done immediately (even if there remain 174 // running operations). It is because in the callback, this instance is 175 // deleted. 176 Done(error); 177 return; 178 } 179 180 ProcessPendingFiles(); 181 } 182 183 void RecursiveOperationDelegate::ProcessSubDirectory() { 184 DCHECK(pending_files_.empty()); 185 DCHECK(!pending_directory_stack_.empty()); 186 DCHECK_EQ(0, inflight_operations_); 187 188 if (canceled_) { 189 Done(base::PLATFORM_FILE_ERROR_ABORT); 190 return; 191 } 192 193 if (!pending_directory_stack_.top().empty()) { 194 // There remain some sub directories. Process them first. 195 ProcessNextDirectory(); 196 return; 197 } 198 199 // All subdirectories are processed. 200 pending_directory_stack_.pop(); 201 if (pending_directory_stack_.empty()) { 202 // All files/directories are processed. 203 Done(base::PLATFORM_FILE_OK); 204 return; 205 } 206 207 DCHECK(!pending_directory_stack_.top().empty()); 208 ++inflight_operations_; 209 PostProcessDirectory( 210 pending_directory_stack_.top().front(), 211 base::Bind(&RecursiveOperationDelegate::DidPostProcessDirectory, 212 AsWeakPtr())); 213 } 214 215 void RecursiveOperationDelegate::DidPostProcessDirectory( 216 base::PlatformFileError error) { 217 DCHECK(pending_files_.empty()); 218 DCHECK(!pending_directory_stack_.empty()); 219 DCHECK(!pending_directory_stack_.top().empty()); 220 DCHECK_EQ(1, inflight_operations_); 221 222 --inflight_operations_; 223 pending_directory_stack_.top().pop(); 224 if (canceled_ || error != base::PLATFORM_FILE_OK) { 225 Done(error); 226 return; 227 } 228 229 ProcessSubDirectory(); 230 } 231 232 void RecursiveOperationDelegate::Done(base::PlatformFileError error) { 233 if (canceled_ && error == base::PLATFORM_FILE_OK) { 234 callback_.Run(base::PLATFORM_FILE_ERROR_ABORT); 235 } else { 236 callback_.Run(error); 237 } 238 } 239 240 } // namespace fileapi 241