Upstreaming browser/ui/uikit_ui_util from iOS.
[chromium-blink-merge.git] / media / cdm / ppapi / cdm_file_io_impl.cc
blobd173788580a385d38155a5cd8afb6ed0a7aeebd7
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.
5 #include "media/cdm/ppapi/cdm_file_io_impl.h"
7 #include <sstream>
9 #include "media/cdm/ppapi/cdm_logging.h"
10 #include "ppapi/c/pp_errors.h"
11 #include "ppapi/cpp/dev/url_util_dev.h"
13 namespace media {
15 // Arbitrary choice based on the following heuristic ideas:
16 // - not too big to avoid unnecessarily large memory allocation;
17 // - not too small to avoid breaking most reads into multiple read operations.
18 const int kReadSize = 8 * 1024;
20 // Call func_call and check the result. If the result is not
21 // PP_OK_COMPLETIONPENDING, print out logs, call OnError() and return.
22 #define CHECK_PP_OK_COMPLETIONPENDING(func_call, error_type) \
23 do { \
24 int32_t result = func_call; \
25 PP_DCHECK(result != PP_OK); \
26 if (result != PP_OK_COMPLETIONPENDING) { \
27 CDM_DLOG() << #func_call << " failed with result: " << result; \
28 state_ = STATE_ERROR; \
29 OnError(error_type); \
30 return; \
31 } \
32 } while (0)
34 #if !defined(NDEBUG)
35 // PPAPI calls should only be made on the main thread. In this file, main thread
36 // checking is only performed in public APIs and the completion callbacks. This
37 // ensures all functions are running on the main thread since internal methods
38 // are called either by the public APIs or by the completion callbacks.
39 static bool IsMainThread() {
40 return pp::Module::Get()->core()->IsMainThread();
42 #endif // !defined(NDEBUG)
44 // Posts a task to run |cb| on the main thread. The task is posted even if the
45 // current thread is the main thread.
46 static void PostOnMain(pp::CompletionCallback cb) {
47 pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK);
50 CdmFileIOImpl::FileLockMap* CdmFileIOImpl::file_lock_map_ = NULL;
52 CdmFileIOImpl::ResourceTracker::ResourceTracker() {
53 // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_
54 // in CdmFileIOImpl::AcquireFileLock().
57 CdmFileIOImpl::ResourceTracker::~ResourceTracker() {
58 delete CdmFileIOImpl::file_lock_map_;
61 CdmFileIOImpl::CdmFileIOImpl(
62 cdm::FileIOClient* client,
63 PP_Instance pp_instance,
64 const pp::CompletionCallback& first_file_read_cb)
65 : state_(STATE_UNOPENED),
66 client_(client),
67 pp_instance_handle_(pp_instance),
68 io_offset_(0),
69 first_file_read_reported_(false),
70 first_file_read_cb_(first_file_read_cb),
71 callback_factory_(this) {
72 PP_DCHECK(IsMainThread());
73 PP_DCHECK(pp_instance); // 0 indicates a "NULL handle".
76 CdmFileIOImpl::~CdmFileIOImpl() {
77 // The destructor is private. |this| can only be destructed through Close().
78 PP_DCHECK(state_ == STATE_CLOSED);
81 // Call sequence: Open() -> OpenFileSystem() -> STATE_FILE_SYSTEM_OPENED.
82 // Note: This only stores file name and opens the file system. The real file
83 // open is deferred to when Read() or Write() is called.
84 void CdmFileIOImpl::Open(const char* file_name, uint32_t file_name_size) {
85 CDM_DLOG() << __FUNCTION__;
86 PP_DCHECK(IsMainThread());
88 if (state_ != STATE_UNOPENED) {
89 CDM_DLOG() << "Open() called in an invalid state.";
90 OnError(OPEN_ERROR);
91 return;
94 // File name should not (1) be empty, (2) start with '_', or (3) contain any
95 // path separators.
96 std::string file_name_str(file_name, file_name_size);
97 if (file_name_str.empty() ||
98 file_name_str[0] == '_' ||
99 file_name_str.find('/') != std::string::npos ||
100 file_name_str.find('\\') != std::string::npos) {
101 CDM_DLOG() << "Invalid file name.";
102 state_ = STATE_ERROR;
103 OnError(OPEN_ERROR);
104 return;
107 // pp::FileRef only accepts path that begins with a '/' character.
108 file_name_ = '/' + file_name_str;
110 if (!AcquireFileLock()) {
111 CDM_DLOG() << "File is in use by other cdm::FileIO objects.";
112 OnError(OPEN_WHILE_IN_USE);
113 return;
116 state_ = STATE_OPENING_FILE_SYSTEM;
117 OpenFileSystem();
120 // Call sequence:
121 // Read() -> OpenFileForRead() -> ReadFile() -> Done.
122 void CdmFileIOImpl::Read() {
123 CDM_DLOG() << __FUNCTION__;
124 PP_DCHECK(IsMainThread());
126 if (state_ == STATE_READING || state_ == STATE_WRITING) {
127 CDM_DLOG() << "Read() called during pending read/write.";
128 OnError(READ_WHILE_IN_USE);
129 return;
132 if (state_ != STATE_FILE_SYSTEM_OPENED) {
133 CDM_DLOG() << "Read() called in an invalid state.";
134 OnError(READ_ERROR);
135 return;
138 PP_DCHECK(io_offset_ == 0);
139 PP_DCHECK(io_buffer_.empty());
140 PP_DCHECK(cumulative_read_buffer_.empty());
141 io_buffer_.resize(kReadSize);
142 io_offset_ = 0;
144 state_ = STATE_READING;
145 OpenFileForRead();
148 // Call sequence:
149 // Write() -> OpenTempFileForWrite() -> WriteTempFile() -> RenameTempFile().
150 // The file name of the temporary file is /_<requested_file_name>.
151 void CdmFileIOImpl::Write(const uint8_t* data, uint32_t data_size) {
152 CDM_DLOG() << __FUNCTION__;
153 PP_DCHECK(IsMainThread());
155 if (state_ == STATE_READING || state_ == STATE_WRITING) {
156 CDM_DLOG() << "Write() called during pending read/write.";
157 OnError(WRITE_WHILE_IN_USE);
158 return;
161 if (state_ != STATE_FILE_SYSTEM_OPENED) {
162 CDM_DLOG() << "Write() called in an invalid state.";
163 OnError(WRITE_ERROR);
164 return;
167 PP_DCHECK(io_offset_ == 0);
168 PP_DCHECK(io_buffer_.empty());
169 if (data_size > 0)
170 io_buffer_.assign(data, data + data_size);
171 else
172 PP_DCHECK(!data);
174 state_ = STATE_WRITING;
175 OpenTempFileForWrite();
178 void CdmFileIOImpl::Close() {
179 CDM_DLOG() << __FUNCTION__;
180 PP_DCHECK(IsMainThread());
181 PP_DCHECK(state_ != STATE_CLOSED);
182 Reset();
183 state_ = STATE_CLOSED;
184 ReleaseFileLock();
185 // All pending callbacks are canceled since |callback_factory_| is destroyed.
186 delete this;
189 bool CdmFileIOImpl::SetFileID() {
190 PP_DCHECK(file_id_.empty());
191 PP_DCHECK(!file_name_.empty() && file_name_[0] == '/');
193 // Not taking ownership of |url_util_dev| (which is a singleton).
194 const pp::URLUtil_Dev* url_util_dev = pp::URLUtil_Dev::Get();
195 PP_URLComponents_Dev components;
196 pp::Var url_var =
197 url_util_dev->GetDocumentURL(pp_instance_handle_, &components);
198 if (!url_var.is_string())
199 return false;
200 std::string url = url_var.AsString();
202 file_id_.append(url, components.scheme.begin, components.scheme.len);
203 file_id_ += ':';
204 file_id_.append(url, components.host.begin, components.host.len);
205 file_id_ += ':';
206 file_id_.append(url, components.port.begin, components.port.len);
207 file_id_ += file_name_;
209 return true;
212 bool CdmFileIOImpl::AcquireFileLock() {
213 PP_DCHECK(IsMainThread());
215 if (file_id_.empty() && !SetFileID())
216 return false;
218 if (!file_lock_map_) {
219 file_lock_map_ = new FileLockMap();
220 } else {
221 FileLockMap::iterator found = file_lock_map_->find(file_id_);
222 if (found != file_lock_map_->end() && found->second)
223 return false;
226 (*file_lock_map_)[file_id_] = true;
227 return true;
230 void CdmFileIOImpl::ReleaseFileLock() {
231 PP_DCHECK(IsMainThread());
233 if (!file_lock_map_)
234 return;
236 FileLockMap::iterator found = file_lock_map_->find(file_id_);
237 if (found != file_lock_map_->end() && found->second)
238 found->second = false;
241 void CdmFileIOImpl::OpenFileSystem() {
242 PP_DCHECK(state_ == STATE_OPENING_FILE_SYSTEM);
244 pp::CompletionCallbackWithOutput<pp::FileSystem> cb =
245 callback_factory_.NewCallbackWithOutput(
246 &CdmFileIOImpl::OnFileSystemOpened);
247 isolated_file_system_ = pp::IsolatedFileSystemPrivate(
248 pp_instance_handle_, PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE);
250 CHECK_PP_OK_COMPLETIONPENDING(isolated_file_system_.Open(cb), OPEN_ERROR);
253 void CdmFileIOImpl::OnFileSystemOpened(int32_t result,
254 pp::FileSystem file_system) {
255 PP_DCHECK(IsMainThread());
256 PP_DCHECK(state_ == STATE_OPENING_FILE_SYSTEM);
258 if (result != PP_OK) {
259 CDM_DLOG() << "File system open failed asynchronously.";
260 ReleaseFileLock();
261 state_ = STATE_ERROR;
262 OnError(OPEN_ERROR);
263 return;
266 file_system_ = file_system;
268 state_ = STATE_FILE_SYSTEM_OPENED;
269 client_->OnOpenComplete(cdm::FileIOClient::kSuccess);
272 void CdmFileIOImpl::OpenFileForRead() {
273 PP_DCHECK(state_ == STATE_READING);
275 PP_DCHECK(file_io_.is_null());
276 PP_DCHECK(file_ref_.is_null());
277 file_io_ = pp::FileIO(pp_instance_handle_);
278 file_ref_ = pp::FileRef(file_system_, file_name_.c_str());
280 // Open file for read. If file doesn't exist, PP_ERROR_FILENOTFOUND will be
281 // returned.
282 int32_t file_open_flag = PP_FILEOPENFLAG_READ;
284 pp::CompletionCallback cb =
285 callback_factory_.NewCallback(&CdmFileIOImpl::OnFileOpenedForRead);
286 CHECK_PP_OK_COMPLETIONPENDING(file_io_.Open(file_ref_, file_open_flag, cb),
287 READ_ERROR);
290 void CdmFileIOImpl::OnFileOpenedForRead(int32_t result) {
291 CDM_DLOG() << __FUNCTION__ << ": " << result;
292 PP_DCHECK(IsMainThread());
293 PP_DCHECK(state_ == STATE_READING);
295 if (result != PP_OK && result != PP_ERROR_FILENOTFOUND) {
296 CDM_DLOG() << "File open failed.";
297 state_ = STATE_ERROR;
298 OnError(OPEN_ERROR);
299 return;
302 // File doesn't exist.
303 if (result == PP_ERROR_FILENOTFOUND) {
304 Reset();
305 state_ = STATE_FILE_SYSTEM_OPENED;
306 client_->OnReadComplete(cdm::FileIOClient::kSuccess, NULL, 0);
307 return;
310 ReadFile();
313 // Call sequence:
314 // fully read
315 // ReadFile() ---> OnFileRead() ------------> Done.
316 // ^ |
317 // | partially read |
318 // |----------------|
319 void CdmFileIOImpl::ReadFile() {
320 PP_DCHECK(state_ == STATE_READING);
321 PP_DCHECK(!io_buffer_.empty());
323 pp::CompletionCallback cb =
324 callback_factory_.NewCallback(&CdmFileIOImpl::OnFileRead);
325 CHECK_PP_OK_COMPLETIONPENDING(
326 file_io_.Read(io_offset_, &io_buffer_[0], io_buffer_.size(), cb),
327 READ_ERROR);
330 void CdmFileIOImpl::OnFileRead(int32_t bytes_read) {
331 CDM_DLOG() << __FUNCTION__ << ": " << bytes_read;
332 PP_DCHECK(IsMainThread());
333 PP_DCHECK(state_ == STATE_READING);
335 // 0 |bytes_read| indicates end-of-file reached.
336 if (bytes_read < PP_OK) {
337 CDM_DLOG() << "Read file failed.";
338 state_ = STATE_ERROR;
339 OnError(READ_ERROR);
340 return;
343 PP_DCHECK(static_cast<size_t>(bytes_read) <= io_buffer_.size());
344 // Append |bytes_read| in |io_buffer_| to |cumulative_read_buffer_|.
345 cumulative_read_buffer_.insert(cumulative_read_buffer_.end(),
346 io_buffer_.begin(),
347 io_buffer_.begin() + bytes_read);
348 io_offset_ += bytes_read;
350 // Not received end-of-file yet. Keep reading.
351 if (bytes_read > 0) {
352 ReadFile();
353 return;
356 // We hit end-of-file. Return read data to the client.
358 // Clear |cumulative_read_buffer_| in case OnReadComplete() calls Read() or
359 // Write().
360 std::vector<char> local_buffer;
361 std::swap(cumulative_read_buffer_, local_buffer);
363 const uint8_t* data = local_buffer.empty() ?
364 NULL : reinterpret_cast<const uint8_t*>(&local_buffer[0]);
366 // Call this before OnReadComplete() so that we always have the latest file
367 // size before CDM fires errors.
368 if (!first_file_read_reported_) {
369 first_file_read_cb_.Run(local_buffer.size());
370 first_file_read_reported_ = true;
373 Reset();
375 state_ = STATE_FILE_SYSTEM_OPENED;
376 client_->OnReadComplete(
377 cdm::FileIOClient::kSuccess, data, local_buffer.size());
380 void CdmFileIOImpl::OpenTempFileForWrite() {
381 PP_DCHECK(state_ == STATE_WRITING);
383 PP_DCHECK(file_name_.size() > 1 && file_name_[0] == '/');
384 // Temporary file name format: /_<requested_file_name>
385 std::string temp_file_name = "/_" + file_name_.substr(1);
387 PP_DCHECK(file_io_.is_null());
388 PP_DCHECK(file_ref_.is_null());
389 file_io_ = pp::FileIO(pp_instance_handle_);
390 file_ref_ = pp::FileRef(file_system_, temp_file_name.c_str());
392 // Create the file if it doesn't exist. Truncate the file to length 0 if it
393 // exists.
394 // TODO(xhwang): Find a good way to report to UMA cases where the temporary
395 // file already exists (due to previous interruption or failure).
396 int32_t file_open_flag = PP_FILEOPENFLAG_WRITE |
397 PP_FILEOPENFLAG_TRUNCATE |
398 PP_FILEOPENFLAG_CREATE;
400 pp::CompletionCallback cb =
401 callback_factory_.NewCallback(&CdmFileIOImpl::OnTempFileOpenedForWrite);
402 CHECK_PP_OK_COMPLETIONPENDING(
403 file_io_.Open(file_ref_, file_open_flag, cb), WRITE_ERROR);
406 void CdmFileIOImpl::OnTempFileOpenedForWrite(int32_t result) {
407 CDM_DLOG() << __FUNCTION__ << ": " << result;
408 PP_DCHECK(IsMainThread());
409 PP_DCHECK(state_ == STATE_WRITING);
411 if (result != PP_OK) {
412 CDM_DLOG() << "Open temporary file failed.";
413 state_ = STATE_ERROR;
414 OnError(WRITE_ERROR);
415 return;
418 // We were told to write 0 bytes (to clear the file). In this case, there's
419 // no need to write anything.
420 if (io_buffer_.empty()) {
421 RenameTempFile();
422 return;
425 PP_DCHECK(io_offset_ == 0);
426 io_offset_ = 0;
427 WriteTempFile();
430 // Call sequence:
431 // fully written
432 // WriteTempFile() -> OnTempFileWritten() ---------------> RenameTempFile().
433 // ^ |
434 // | partially written |
435 // |---------------------|
436 void CdmFileIOImpl::WriteTempFile() {
437 PP_DCHECK(state_ == STATE_WRITING);
438 PP_DCHECK(io_offset_ < io_buffer_.size());
440 pp::CompletionCallback cb =
441 callback_factory_.NewCallback(&CdmFileIOImpl::OnTempFileWritten);
442 CHECK_PP_OK_COMPLETIONPENDING(file_io_.Write(io_offset_,
443 &io_buffer_[io_offset_],
444 io_buffer_.size() - io_offset_,
445 cb),
446 WRITE_ERROR);
449 void CdmFileIOImpl::OnTempFileWritten(int32_t bytes_written) {
450 CDM_DLOG() << __FUNCTION__ << ": " << bytes_written;
451 PP_DCHECK(IsMainThread());
452 PP_DCHECK(state_ == STATE_WRITING);
454 if (bytes_written <= PP_OK) {
455 CDM_DLOG() << "Write temporary file failed.";
456 state_ = STATE_ERROR;
457 OnError(WRITE_ERROR);
458 return;
461 io_offset_ += bytes_written;
462 PP_DCHECK(io_offset_ <= io_buffer_.size());
464 if (io_offset_ < io_buffer_.size()) {
465 WriteTempFile();
466 return;
469 // All data written. Now rename the temporary file to the real file.
470 RenameTempFile();
473 void CdmFileIOImpl::RenameTempFile() {
474 PP_DCHECK(state_ == STATE_WRITING);
476 pp::CompletionCallback cb =
477 callback_factory_.NewCallback(&CdmFileIOImpl::OnTempFileRenamed);
478 CHECK_PP_OK_COMPLETIONPENDING(
479 file_ref_.Rename(pp::FileRef(file_system_, file_name_.c_str()), cb),
480 WRITE_ERROR);
483 void CdmFileIOImpl::OnTempFileRenamed(int32_t result) {
484 CDM_DLOG() << __FUNCTION__ << ": " << result;
485 PP_DCHECK(IsMainThread());
486 PP_DCHECK(state_ == STATE_WRITING);
488 if (result != PP_OK) {
489 CDM_DLOG() << "Rename temporary file failed.";
490 state_ = STATE_ERROR;
491 OnError(WRITE_ERROR);
492 return;
495 Reset();
497 state_ = STATE_FILE_SYSTEM_OPENED;
498 client_->OnWriteComplete(cdm::FileIOClient::kSuccess);
501 void CdmFileIOImpl::Reset() {
502 PP_DCHECK(IsMainThread());
503 io_buffer_.clear();
504 io_offset_ = 0;
505 cumulative_read_buffer_.clear();
506 file_io_.Close();
507 file_io_ = pp::FileIO();
508 file_ref_ = pp::FileRef();
511 void CdmFileIOImpl::OnError(ErrorType error_type) {
512 // For *_WHILE_IN_USE errors, do not reset these values. Otherwise, the
513 // existing read/write operation will fail.
514 if (error_type == READ_ERROR || error_type == WRITE_ERROR)
515 Reset();
517 PostOnMain(callback_factory_.NewCallback(&CdmFileIOImpl::NotifyClientOfError,
518 error_type));
521 void CdmFileIOImpl::NotifyClientOfError(int32_t result,
522 ErrorType error_type) {
523 PP_DCHECK(result == PP_OK);
524 switch (error_type) {
525 case OPEN_ERROR:
526 client_->OnOpenComplete(cdm::FileIOClient::kError);
527 break;
528 case READ_ERROR:
529 client_->OnReadComplete(cdm::FileIOClient::kError, NULL, 0);
530 break;
531 case WRITE_ERROR:
532 client_->OnWriteComplete(cdm::FileIOClient::kError);
533 break;
534 case OPEN_WHILE_IN_USE:
535 client_->OnOpenComplete(cdm::FileIOClient::kInUse);
536 break;
537 case READ_WHILE_IN_USE:
538 client_->OnReadComplete(cdm::FileIOClient::kInUse, NULL, 0);
539 break;
540 case WRITE_WHILE_IN_USE:
541 client_->OnWriteComplete(cdm::FileIOClient::kInUse);
542 break;
546 } // namespace media