| // Copyright 2015 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "cobalt/loader/file_fetcher.h" |
| |
| #include "base/bind.h" |
| #include "base/files/file_util.h" |
| #include "base/location.h" |
| #include "base/path_service.h" |
| #include "cobalt/base/cobalt_paths.h" |
| |
| namespace cobalt { |
| namespace loader { |
| namespace { |
| const char* FileErrorToString(base::File::Error error) { |
| static const char kPlatformFileOk[] = "Platform file OK"; |
| static const char kPlatformFileErrorNotFound[] = |
| "Platform file error: Not found"; |
| static const char kPlatformFileErrorInUse[] = "Platform file error: In use"; |
| static const char kPlatformFileErrorAccessDenied[] = |
| "Platform file error: Access denied"; |
| static const char kPlatformFileErrorSecurity[] = |
| "Platform file error: Security"; |
| static const char kPlatformFileErrorInvalidUrl[] = |
| "Platform file error: Invalid URL"; |
| static const char kPlatformFileErrorAbort[] = "Platform file error: Abort"; |
| static const char kPlatformFileErrorNotAFile[] = |
| "Platform file error: Not a file"; |
| static const char kPlatformFileErrorNotDefined[] = |
| "Platform file error: Undefined error"; |
| |
| switch (error) { |
| case base::File::FILE_OK: |
| return kPlatformFileOk; |
| case base::File::FILE_ERROR_NOT_FOUND: |
| return kPlatformFileErrorNotFound; |
| case base::File::FILE_ERROR_IN_USE: |
| return kPlatformFileErrorInUse; |
| case base::File::FILE_ERROR_ACCESS_DENIED: |
| return kPlatformFileErrorAccessDenied; |
| case base::File::FILE_ERROR_SECURITY: |
| return kPlatformFileErrorSecurity; |
| case base::File::FILE_ERROR_INVALID_URL: |
| return kPlatformFileErrorInvalidUrl; |
| case base::File::FILE_ERROR_ABORT: |
| return kPlatformFileErrorAbort; |
| case base::File::FILE_ERROR_NOT_A_FILE: |
| return kPlatformFileErrorNotAFile; |
| case base::File::FILE_ERROR_FAILED: |
| case base::File::FILE_ERROR_EXISTS: |
| case base::File::FILE_ERROR_TOO_MANY_OPENED: |
| case base::File::FILE_ERROR_NO_MEMORY: |
| case base::File::FILE_ERROR_NO_SPACE: |
| case base::File::FILE_ERROR_NOT_A_DIRECTORY: |
| case base::File::FILE_ERROR_INVALID_OPERATION: |
| case base::File::FILE_ERROR_NOT_EMPTY: |
| case base::File::FILE_ERROR_IO: |
| case base::File::FILE_ERROR_MAX: |
| break; |
| } |
| return kPlatformFileErrorNotDefined; |
| } |
| } // namespace |
| |
| FileFetcher::FileFetcher(const base::FilePath& file_path, Handler* handler, |
| const Options& options) |
| : Fetcher(handler), |
| buffer_size_(options.buffer_size), |
| file_(base::kInvalidPlatformFile), |
| file_offset_(options.start_offset), |
| bytes_left_to_read_(options.bytes_to_read), |
| task_runner_(options.message_loop_proxy), |
| file_path_(file_path), |
| ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)), |
| file_proxy_(task_runner_.get()) { |
| DCHECK_GT(buffer_size_, 0); |
| |
| // Ensure the request does not attempt to navigate outside the whitelisted |
| // directory. |
| if (file_path_.ReferencesParent() || file_path_.IsAbsolute()) { |
| handler->OnError(this, |
| FileErrorToString(base::File::FILE_ERROR_ACCESS_DENIED)); |
| return; |
| } |
| |
| // Try fetching the file from each search path entry in turn. |
| // Start at the beginning. On failure, we'll try the next path entry, |
| // and so on until we open the file or reach the end of the search path. |
| BuildSearchPath(options.extra_search_dir); |
| curr_search_path_iter_ = search_path_.begin(); |
| TryFileOpen(); |
| } |
| |
| FileFetcher::~FileFetcher() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (task_runner_ != base::MessageLoop::current()->task_runner()) { |
| // In case we are currently in the middle of a fetch (in which case it will |
| // be aborted), invalidate the weak pointers to this FileFetcher object to |
| // ensure that we do not process any responses from pending file I/O, which |
| // also guarantees that no new file I/O requests will be generated after the |
| // current one (if one is currently active). |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| // Then wait for any currently active file I/O to complete, after which |
| // everything will be quiet and we can shutdown safely. |
| task_runner_->WaitForFence(); |
| } |
| } |
| |
| void FileFetcher::BuildSearchPath(const base::FilePath& extra_search_dir) { |
| // Build the vector of paths to search for files. |
| // Paths will be tried in the order they are listed. |
| // Add the user-specified extra directory first, if specified, |
| // so it has precedence. |
| if (!extra_search_dir.empty()) { |
| search_path_.push_back(extra_search_dir); |
| } |
| |
| base::FilePath search_dir; |
| base::PathService::Get(paths::DIR_COBALT_WEB_ROOT, &search_dir); |
| search_path_.push_back(search_dir); |
| |
| // We also search DIR_TEST_DATA in non-release builds. |
| #if defined(ENABLE_TEST_DATA) |
| base::PathService::Get(base::DIR_TEST_DATA, &search_dir); |
| search_path_.push_back(search_dir); |
| #endif // ENABLE_TEST_DATA |
| } |
| |
| void FileFetcher::TryFileOpen() { |
| // Append the file path to the current search path entry and try. |
| base::FilePath actual_file_path; |
| actual_file_path = curr_search_path_iter_->Append(file_path_); |
| |
| file_proxy_.CreateOrOpen(actual_file_path, |
| base::File::FLAG_OPEN | base::File::FLAG_READ, |
| base::Bind(&FileFetcher::DidCreateOrOpen, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void FileFetcher::ReadNextChunk() { |
| int32 bytes_to_read = buffer_size_; |
| if (bytes_to_read > bytes_left_to_read_) { |
| bytes_to_read = static_cast<int32>(bytes_left_to_read_); |
| } |
| file_proxy_.Read( |
| file_offset_, bytes_to_read, |
| base::Bind(&FileFetcher::DidRead, weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void FileFetcher::DidCreateOrOpen(base::File::Error error) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (error != base::File::FILE_OK) { |
| // File could not be opened at the current search path entry. |
| // Try the next, or if we've searched the whole path, signal error. |
| if (++curr_search_path_iter_ != search_path_.end()) { |
| TryFileOpen(); |
| } else { |
| // base::File::Error and Starboard file error can cast to each other. |
| handler()->OnError(this, FileErrorToString(error)); |
| } |
| return; |
| } |
| ReadNextChunk(); |
| } |
| |
| void FileFetcher::DidRead(base::File::Error error, const char* data, |
| int num_bytes_read) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (error != base::File::FILE_OK) { |
| handler()->OnError(this, FileErrorToString(error)); |
| return; |
| } |
| |
| DCHECK_LE(num_bytes_read, bytes_left_to_read_); |
| |
| if (!num_bytes_read) { |
| handler()->OnDone(this); |
| return; |
| } |
| |
| handler()->OnReceived(this, data, static_cast<size_t>(num_bytes_read)); |
| |
| bytes_left_to_read_ -= num_bytes_read; |
| |
| if (!bytes_left_to_read_) { |
| handler()->OnDone(this); |
| return; |
| } |
| |
| file_offset_ += num_bytes_read; |
| ReadNextChunk(); |
| } |
| |
| } // namespace loader |
| } // namespace cobalt |