OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/chromeos/gdata/gdata_files.h" | 5 #include "chrome/browser/chromeos/gdata/gdata_files.h" |
6 | 6 |
7 #include <leveldb/db.h> | |
7 #include <vector> | 8 #include <vector> |
8 | 9 |
9 #include "base/message_loop_proxy.h" | 10 #include "base/message_loop_proxy.h" |
10 #include "base/platform_file.h" | 11 #include "base/platform_file.h" |
12 #include "base/string_number_conversions.h" | |
11 #include "base/string_util.h" | 13 #include "base/string_util.h" |
12 #include "base/stringprintf.h" | 14 #include "base/stringprintf.h" |
15 #include "base/sequenced_task_runner.h" | |
13 #include "base/tracked_objects.h" | 16 #include "base/tracked_objects.h" |
14 #include "base/utf_string_conversions.h" | 17 #include "base/utf_string_conversions.h" |
15 #include "chrome/browser/chromeos/gdata/gdata.pb.h" | 18 #include "chrome/browser/chromeos/gdata/gdata.pb.h" |
16 #include "chrome/browser/chromeos/gdata/gdata_util.h" | 19 #include "chrome/browser/chromeos/gdata/gdata_util.h" |
17 #include "chrome/browser/chromeos/gdata/gdata_wapi_parser.h" | 20 #include "chrome/browser/chromeos/gdata/gdata_wapi_parser.h" |
21 #include "content/public/browser/browser_thread.h" | |
18 #include "net/base/escape.h" | 22 #include "net/base/escape.h" |
19 | 23 |
24 using content::BrowserThread; | |
25 | |
20 namespace gdata { | 26 namespace gdata { |
21 namespace { | 27 namespace { |
22 | 28 |
23 const char kSlash[] = "/"; | 29 const char kSlash[] = "/"; |
24 const char kEscapedSlash[] = "\xE2\x88\x95"; | 30 const char kEscapedSlash[] = "\xE2\x88\x95"; |
25 | 31 |
26 // Extracts resource_id out of edit url. | 32 // Extracts resource_id out of edit url. |
27 std::string ExtractResourceId(const GURL& url) { | 33 std::string ExtractResourceId(const GURL& url) { |
28 return net::UnescapeURLComponent(url.ExtractFileName(), | 34 return net::UnescapeURLComponent(url.ExtractFileName(), |
29 net::UnescapeRule::URL_SPECIAL_CHARS); | 35 net::UnescapeRule::URL_SPECIAL_CHARS); |
(...skipping 386 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
416 GDataDirectory* dir = iter->second; | 422 GDataDirectory* dir = iter->second; |
417 // Remove directories recursively. | 423 // Remove directories recursively. |
418 dir->RemoveChildren(); | 424 dir->RemoveChildren(); |
419 if (directory_service_) | 425 if (directory_service_) |
420 directory_service_->RemoveEntryFromResourceMap(dir); | 426 directory_service_->RemoveEntryFromResourceMap(dir); |
421 } | 427 } |
422 STLDeleteValues(&child_directories_); | 428 STLDeleteValues(&child_directories_); |
423 child_directories_.clear(); | 429 child_directories_.clear(); |
424 } | 430 } |
425 | 431 |
432 // GDataDirectoryServiceDB implementation. | |
433 | |
434 // Params for GDatadirectoryServiceDB::Create. | |
435 struct CreateDBParams { | |
436 CreateDBParams(const FilePath& db_path, | |
437 base::SequencedTaskRunner* blocking_task_runner) | |
438 : db_path(db_path), | |
439 blocking_task_runner(blocking_task_runner) { | |
440 } | |
441 | |
442 FilePath db_path; | |
443 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner; | |
444 scoped_ptr<GDataDirectoryServiceDB> db; | |
445 GDataDirectoryService::SerializedMap serialized_resources; | |
446 }; | |
447 | |
448 // Wrapper for level db. All methods must be called on blocking thread. | |
449 class GDataDirectoryServiceDB { | |
satorux1
2012/08/03 06:32:50
From this name, I originally thought this is a sub
achuithb
2012/08/03 19:15:59
I like ResourceMetadataDB. I've done the rename.
| |
450 public: | |
451 GDataDirectoryServiceDB(const FilePath& db_path, | |
452 base::SequencedTaskRunner* blocking_task_runner); | |
453 | |
454 // Initializes the database. | |
455 void Init(); | |
456 | |
457 // Reads the database into |serialized_resources|. | |
458 void Read(GDataDirectoryService::SerializedMap* serialized_resources); | |
459 | |
460 // Saves |serialized_resources| to the database. | |
461 void Save(const GDataDirectoryService::SerializedMap& serialized_resources); | |
462 | |
463 private: | |
464 // Clears the database. | |
465 void Clear(); | |
466 | |
467 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner_; | |
468 scoped_ptr<leveldb::DB> level_db_; | |
469 FilePath db_path_; | |
470 }; | |
471 | |
472 GDataDirectoryServiceDB::GDataDirectoryServiceDB(const FilePath& db_path, | |
473 base::SequencedTaskRunner* blocking_task_runner) | |
474 : blocking_task_runner_(blocking_task_runner), | |
475 db_path_(db_path) { | |
476 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); | |
477 } | |
478 | |
479 // Creates, initializes and reads from the database. | |
480 // This must be defined after GDataDirectoryServiceDB and CreateDBParams. | |
481 static void CreateGDataDirectoryServiceDBOnBlockingPool( | |
482 CreateDBParams* params) { | |
483 DCHECK(params->blocking_task_runner->RunsTasksOnCurrentThread()); | |
484 DCHECK(!params->db_path.empty()); | |
485 | |
486 params->db.reset(new GDataDirectoryServiceDB(params->db_path, | |
487 params->blocking_task_runner)); | |
488 params->db->Init(); | |
489 params->db->Read(¶ms->serialized_resources); | |
490 } | |
491 | |
492 void GDataDirectoryServiceDB::Init() { | |
493 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); | |
494 DCHECK(!db_path_.empty()); | |
495 | |
496 DVLOG(1) << "Init " << db_path_.value(); | |
497 | |
498 leveldb::DB* level_db = NULL; | |
499 leveldb::Options options; | |
500 options.create_if_missing = true; | |
501 leveldb::Status db_status = leveldb::DB::Open(options, db_path_.value(), | |
502 &level_db); | |
503 DCHECK(level_db); | |
504 DCHECK(db_status.ok()); | |
505 level_db_.reset(level_db); | |
506 } | |
507 | |
508 void GDataDirectoryServiceDB::Read( | |
509 GDataDirectoryService::SerializedMap* serialized_resources) { | |
510 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); | |
511 DCHECK(serialized_resources); | |
512 DVLOG(1) << "Read " << db_path_.value(); | |
513 | |
514 scoped_ptr<leveldb::Iterator> iter(level_db_->NewIterator( | |
515 leveldb::ReadOptions())); | |
516 for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { | |
517 DVLOG(1) << "Read, resource " << iter->key().ToString(); | |
518 serialized_resources->insert(std::make_pair(iter->key().ToString(), | |
519 iter->value().ToString())); | |
520 } | |
521 } | |
522 | |
523 void GDataDirectoryServiceDB::Save( | |
524 const GDataDirectoryService::SerializedMap& serialized_resources) { | |
525 DCHECK(blocking_task_runner_->RunsTasksOnCurrentThread()); | |
526 | |
527 Clear(); | |
528 for (GDataDirectoryService::SerializedMap::const_iterator iter = | |
529 serialized_resources.begin(); | |
530 iter != serialized_resources.end(); ++iter) { | |
531 DVLOG(1) << "Saving resource " << iter->first << " to db"; | |
532 level_db_->Put(leveldb::WriteOptions(), | |
satorux1
2012/08/03 06:32:50
What if Put fails? Shouldn't we check the return v
achuithb
2012/08/03 19:15:59
I've added a LOG(error), but I'm not sure what els
| |
533 leveldb::Slice(iter->first), | |
534 leveldb::Slice(iter->second)); | |
535 } | |
536 } | |
537 | |
538 void GDataDirectoryServiceDB::Clear() { | |
539 level_db_.reset(); | |
540 leveldb::DestroyDB(db_path_.value(), leveldb::Options()); | |
541 Init(); | |
542 } | |
543 | |
426 // GDataDirectoryService class implementation. | 544 // GDataDirectoryService class implementation. |
427 | 545 |
428 GDataDirectoryService::GDataDirectoryService() | 546 GDataDirectoryService::GDataDirectoryService() |
429 : serialized_size_(0), | 547 : blocking_task_runner_(NULL), |
548 serialized_size_(0), | |
430 largest_changestamp_(0), | 549 largest_changestamp_(0), |
431 origin_(UNINITIALIZED) { | 550 origin_(UNINITIALIZED), |
551 weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { | |
432 root_.reset(new GDataDirectory(NULL, this)); | 552 root_.reset(new GDataDirectory(NULL, this)); |
433 root_->set_title(kGDataRootDirectory); | 553 root_->set_title(kGDataRootDirectory); |
434 root_->SetBaseNameFromTitle(); | 554 root_->SetBaseNameFromTitle(); |
435 root_->set_resource_id(kGDataRootDirectoryResourceId); | 555 root_->set_resource_id(kGDataRootDirectoryResourceId); |
436 AddEntryToResourceMap(root_.get()); | 556 AddEntryToResourceMap(root_.get()); |
437 } | 557 } |
438 | 558 |
439 GDataDirectoryService::~GDataDirectoryService() { | 559 GDataDirectoryService::~GDataDirectoryService() { |
560 ClearRoot(); | |
561 | |
562 // Ensure db is closed on the blocking pool. | |
563 if (blocking_task_runner_ && directory_service_db_.get()) | |
564 blocking_task_runner_->PostTask(FROM_HERE, | |
satorux1
2012/08/03 06:32:50
I think you can use DelteSoon() instead
achuithb
2012/08/03 19:15:59
Done.
| |
565 base::Bind(&base::DeletePointer<GDataDirectoryServiceDB>, | |
566 directory_service_db_.release())); | |
567 } | |
568 | |
569 void GDataDirectoryService::ClearRoot() { | |
440 // Note that children have a reference to root_, | 570 // Note that children have a reference to root_, |
441 // so we need to delete them here. | 571 // so we need to delete them here. |
442 root_->RemoveChildren(); | 572 root_->RemoveChildren(); |
443 RemoveEntryFromResourceMap(root_.get()); | 573 RemoveEntryFromResourceMap(root_.get()); |
444 DCHECK(resource_map_.empty()); | 574 DCHECK(resource_map_.empty()); |
445 resource_map_.clear(); | 575 resource_map_.clear(); |
576 root_.reset(); | |
446 } | 577 } |
447 | 578 |
448 void GDataDirectoryService::AddEntryToDirectory( | 579 void GDataDirectoryService::AddEntryToDirectory( |
449 const FilePath& directory_path, | 580 const FilePath& directory_path, |
450 GDataEntry* entry, | 581 GDataEntry* entry, |
451 const FileOperationCallback& callback) { | 582 const FileOperationCallback& callback) { |
452 GDataEntry* destination = FindEntryByPathSync(directory_path); | 583 GDataEntry* destination = FindEntryByPathSync(directory_path); |
453 GDataFileError error = GDATA_FILE_ERROR_FAILED; | 584 GDataFileError error = GDATA_FILE_ERROR_FAILED; |
454 if (!destination) { | 585 if (!destination) { |
455 error = GDATA_FILE_ERROR_NOT_FOUND; | 586 error = GDATA_FILE_ERROR_NOT_FOUND; |
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
554 GDataDirectory* entry_parent = old_entry ? old_entry->parent() : NULL; | 685 GDataDirectory* entry_parent = old_entry ? old_entry->parent() : NULL; |
555 if (entry_parent) { | 686 if (entry_parent) { |
556 DCHECK_EQ(fresh_file->resource_id(), old_entry->resource_id()); | 687 DCHECK_EQ(fresh_file->resource_id(), old_entry->resource_id()); |
557 DCHECK(old_entry->AsGDataFile()); | 688 DCHECK(old_entry->AsGDataFile()); |
558 | 689 |
559 entry_parent->RemoveEntry(old_entry); | 690 entry_parent->RemoveEntry(old_entry); |
560 entry_parent->AddEntry(fresh_file.release()); | 691 entry_parent->AddEntry(fresh_file.release()); |
561 } | 692 } |
562 } | 693 } |
563 | 694 |
695 void GDataDirectoryService::InitFromDB( | |
696 const FilePath& db_path, | |
697 base::SequencedTaskRunner* blocking_task_runner, | |
698 LoadRootFeedParams* load_params, | |
699 const base::Closure& callback) { | |
700 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
701 DCHECK(!db_path.empty()); | |
702 DCHECK(blocking_task_runner); | |
703 | |
704 if (directory_service_db_.get()) { | |
705 NOTREACHED(); | |
706 if (!callback.is_null()) | |
707 callback.Run(); | |
708 } | |
709 | |
710 blocking_task_runner_ = blocking_task_runner; | |
711 | |
712 DVLOG(1) << "InitFromDB " << db_path.value(); | |
713 | |
714 CreateDBParams* create_params = | |
715 new CreateDBParams(db_path, blocking_task_runner); | |
716 blocking_task_runner_->PostTaskAndReply( | |
717 FROM_HERE, | |
718 base::Bind(&CreateGDataDirectoryServiceDBOnBlockingPool, | |
719 create_params), | |
720 base::Bind(&GDataDirectoryService::InitResourceMap, | |
721 weak_ptr_factory_.GetWeakPtr(), | |
722 base::Owned(create_params), | |
723 load_params, | |
724 callback)); | |
725 } | |
726 | |
727 void GDataDirectoryService::InitResourceMap( | |
728 CreateDBParams* create_params, | |
729 LoadRootFeedParams* load_params, | |
730 const base::Closure& callback) { | |
731 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
732 DCHECK(create_params); | |
733 // Ensure this callback is run before we return. | |
734 base::ScopedClosureRunner callback_runner(callback); | |
735 | |
736 directory_service_db_ = create_params->db.Pass(); | |
737 if (create_params->serialized_resources.empty()) { | |
738 origin_ = INITIALIZING; | |
739 if (load_params) | |
740 load_params->load_error = GDATA_FILE_ERROR_NOT_FOUND; | |
741 return; | |
742 } | |
743 | |
744 ClearRoot(); | |
745 | |
746 int32 version = 0; | |
747 ResourceMap resource_map; | |
748 for (SerializedMap::const_iterator iter = | |
749 create_params->serialized_resources.begin(); | |
satorux1
2012/08/03 06:32:50
indentation
achuithb
2012/08/03 19:15:59
Done.
| |
750 iter != create_params->serialized_resources.end(); ++iter) { | |
751 if (iter->first == "version") { | |
752 if (!base::StringToInt(iter->second, &version) || | |
753 version != kProtoVersion) { | |
satorux1
2012/08/03 06:32:50
shouldn't we set the load_error to GDATA_FILE_ERRO
achuithb
2012/08/03 19:15:59
Done.
| |
754 return; | |
755 } | |
756 continue; | |
757 } | |
758 | |
759 if (iter->first == "largest_changestamp") { | |
760 base::StringToInt(iter->second, &largest_changestamp_); | |
761 DVLOG(1) << "InitResourceMap largest_changestamp_" | |
762 << largest_changestamp_; | |
satorux1
2012/08/03 06:32:50
ditto. load_error?
achuithb
2012/08/03 19:15:59
I think this is an error but it's not fatal, right
| |
763 continue; | |
764 } | |
765 | |
766 scoped_ptr<GDataEntry> entry = FromProtoString(iter->second); | |
767 if (entry.get()) { | |
768 DVLOG(1) << "Inserting resource " << iter->first | |
769 << " into resource_map"; | |
770 resource_map.insert(std::make_pair(iter->first, entry.release())); | |
771 } else { | |
772 NOTREACHED() << "Failed to parse GDataEntry for resource " | |
773 << iter->first; | |
774 } | |
775 } | |
776 | |
777 // Fix up parent-child relations. | |
778 for (ResourceMap::iterator iter = resource_map.begin(); | |
779 iter != resource_map.end(); ++iter) { | |
780 GDataEntry* entry = iter->second; | |
781 ResourceMap::iterator parent_it = | |
782 resource_map.find(entry->parent_resource_id()); | |
783 if (parent_it != resource_map.end()) { | |
784 GDataDirectory* parent = parent_it->second->AsGDataDirectory(); | |
785 if (parent) { | |
786 DVLOG(1) << "Adding " << entry->resource_id() | |
787 << " as a child of " << parent->resource_id(); | |
788 parent->AddEntry(entry); | |
789 } else { | |
790 NOTREACHED() << "Parent is not a directory " << parent->resource_id(); | |
791 } | |
792 } else if (entry->resource_id() == kGDataRootDirectoryResourceId) { | |
793 root_.reset(entry->AsGDataDirectory()); | |
794 DCHECK(root_.get()); | |
795 AddEntryToResourceMap(root_.get()); | |
796 } else { | |
797 NOTREACHED() << "Missing parent id " << entry->parent_resource_id() | |
798 << " for resource " << entry->resource_id(); | |
799 } | |
800 } | |
801 | |
802 DCHECK(root_.get()); | |
803 DCHECK_EQ(resource_map.size(), resource_map_.size()); | |
804 DCHECK_EQ(resource_map.size(), | |
805 create_params->serialized_resources.size() - 2); | |
806 | |
807 origin_ = FROM_CACHE; | |
808 } | |
809 | |
810 void GDataDirectoryService::SaveToDB() { | |
811 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
812 | |
813 if (!blocking_task_runner_ || !directory_service_db_.get()) { | |
814 NOTREACHED(); | |
815 return; | |
816 } | |
817 | |
818 size_t serialized_size = 0; | |
819 SerializedMap serialized_resources; | |
820 for (ResourceMap::const_iterator iter = resource_map_.begin(); | |
821 iter != resource_map_.end(); ++iter) { | |
822 GDataEntryProto proto; | |
823 iter->second->ToProtoFull(&proto); | |
824 std::string serialized_string; | |
825 const bool ok = proto.SerializeToString(&serialized_string); | |
826 DCHECK(ok); | |
827 if (ok) { | |
828 serialized_resources.insert( | |
829 std::make_pair(iter->first, serialized_string)); | |
830 serialized_size += serialized_string.size(); | |
831 } | |
832 } | |
833 | |
834 serialized_resources.insert(std::make_pair("version", | |
835 base::IntToString(kProtoVersion))); | |
836 serialized_resources.insert(std::make_pair("largest_changestamp", | |
satorux1
2012/08/03 06:32:50
Let's define constants for "version" and "largest_
achuithb
2012/08/03 19:15:59
Done.
| |
837 base::IntToString(largest_changestamp_))); | |
838 set_last_serialized(base::Time::Now()); | |
839 set_serialized_size(serialized_size); | |
840 | |
841 blocking_task_runner_->PostTask( | |
842 FROM_HERE, | |
843 base::Bind(&GDataDirectoryServiceDB::Save, | |
844 base::Unretained(directory_service_db_.get()), | |
845 serialized_resources)); | |
846 } | |
847 | |
564 // Convert to/from proto. | 848 // Convert to/from proto. |
565 | 849 |
566 // static | 850 // static |
567 void GDataEntry::ConvertProtoToPlatformFileInfo( | 851 void GDataEntry::ConvertProtoToPlatformFileInfo( |
568 const PlatformFileInfoProto& proto, | 852 const PlatformFileInfoProto& proto, |
569 base::PlatformFileInfo* file_info) { | 853 base::PlatformFileInfo* file_info) { |
570 file_info->size = proto.size(); | 854 file_info->size = proto.size(); |
571 file_info->is_directory = proto.is_directory(); | 855 file_info->is_directory = proto.is_directory(); |
572 file_info->is_symbolic_link = proto.is_symbolic_link(); | 856 file_info->is_symbolic_link = proto.is_symbolic_link(); |
573 file_info->last_modified = base::Time::FromInternalValue( | 857 file_info->last_modified = base::Time::FromInternalValue( |
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
788 | 1072 |
789 if (!root_->FromProto(proto.gdata_directory())) | 1073 if (!root_->FromProto(proto.gdata_directory())) |
790 return false; | 1074 return false; |
791 | 1075 |
792 origin_ = FROM_CACHE; | 1076 origin_ = FROM_CACHE; |
793 largest_changestamp_ = proto.largest_changestamp(); | 1077 largest_changestamp_ = proto.largest_changestamp(); |
794 | 1078 |
795 return true; | 1079 return true; |
796 } | 1080 } |
797 | 1081 |
1082 scoped_ptr<GDataEntry> GDataDirectoryService::FromProtoString( | |
1083 const std::string& serialized_proto) { | |
1084 scoped_ptr<GDataEntry> entry; | |
1085 | |
1086 GDataEntryProto entry_proto; | |
1087 if (!entry_proto.ParseFromString(serialized_proto)) | |
1088 return entry.Pass(); | |
hashimoto
2012/08/03 05:17:07
Why don't you simply pass scoped_ptr<GDataEntry>()
satorux1
2012/08/03 06:32:50
Agreed with hashimoto. That'll be clearer
achuithb
2012/08/03 19:15:59
Done.
| |
1089 | |
1090 if (entry_proto.file_info().is_directory()) { | |
satorux1
2012/08/03 06:32:50
move GDataEntryProto entry_proto; right before 'if
achuithb
2012/08/03 19:15:59
I don't understand? It's needed for ParseFromStrin
satorux1
2012/08/03 20:19:16
my bad. i was wrong.
| |
1091 entry.reset(new GDataDirectory(NULL, this)); | |
1092 // Call GDataEntry::FromProto. | |
satorux1
2012/08/03 06:32:50
Please add some more comment that why GDtaDirector
achuithb
2012/08/03 19:15:59
Correct. Comment added.
| |
1093 if (!entry->FromProto(entry_proto)) { | |
1094 NOTREACHED() << "FromProto (directory) failed"; | |
1095 entry.reset(); | |
1096 } | |
1097 } else { | |
1098 scoped_ptr<GDataFile> file(new GDataFile(NULL, this)); | |
1099 // Call GDataFile::FromProto. | |
1100 if (file->FromProto(entry_proto)) { | |
1101 entry.reset(file.release()); | |
1102 } else { | |
1103 NOTREACHED() << "FromProto (file) failed"; | |
1104 } | |
1105 } | |
1106 return entry.Pass(); | |
1107 } | |
1108 | |
798 } // namespace gdata | 1109 } // namespace gdata |
OLD | NEW |