| // Copyright (c) 2010 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 "chrome/browser/extensions/extension_bookmarks_module.h" |
| |
| #include "base/json/json_writer.h" |
| #include "base/sha1.h" |
| #include "base/stl_util-inl.h" |
| #include "base/string16.h" |
| #include "base/string_number_conversions.h" |
| #include "base/utf_string_conversions.h" |
| #include "chrome/browser/bookmarks/bookmark_codec.h" |
| #include "chrome/browser/bookmarks/bookmark_html_writer.h" |
| #include "chrome/browser/bookmarks/bookmark_model.h" |
| #include "chrome/browser/bookmarks/bookmark_utils.h" |
| #include "chrome/browser/browser_list.h" |
| #include "chrome/browser/extensions/extension_bookmark_helpers.h" |
| #include "chrome/browser/extensions/extension_bookmarks_module_constants.h" |
| #include "chrome/browser/extensions/extension_event_router.h" |
| #include "chrome/browser/extensions/extensions_quota_service.h" |
| #include "chrome/browser/importer/importer.h" |
| #include "chrome/browser/importer/importer_data_types.h" |
| #include "chrome/browser/prefs/pref_service.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/common/notification_service.h" |
| #include "chrome/common/pref_names.h" |
| |
| namespace keys = extension_bookmarks_module_constants; |
| |
| using base::TimeDelta; |
| typedef QuotaLimitHeuristic::Bucket Bucket; |
| typedef QuotaLimitHeuristic::Config Config; |
| typedef QuotaLimitHeuristic::BucketList BucketList; |
| typedef ExtensionsQuotaService::TimedLimit TimedLimit; |
| typedef ExtensionsQuotaService::SustainedLimit SustainedLimit; |
| typedef QuotaLimitHeuristic::BucketMapper BucketMapper; |
| |
| void BookmarksFunction::Run() { |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| if (!model->IsLoaded()) { |
| // Bookmarks are not ready yet. We'll wait. |
| registrar_.Add(this, NotificationType::BOOKMARK_MODEL_LOADED, |
| NotificationService::AllSources()); |
| AddRef(); // Balanced in Observe(). |
| return; |
| } |
| |
| bool success = RunImpl(); |
| if (success) { |
| NotificationService::current()->Notify( |
| NotificationType::EXTENSION_BOOKMARKS_API_INVOKED, |
| Source<const Extension>(GetExtension()), |
| Details<const BookmarksFunction>(this)); |
| } |
| SendResponse(success); |
| } |
| |
| bool BookmarksFunction::GetBookmarkIdAsInt64( |
| const std::string& id_string, int64* id) { |
| if (base::StringToInt64(id_string, id)) |
| return true; |
| |
| error_ = keys::kInvalidIdError; |
| return false; |
| } |
| |
| void BookmarksFunction::Observe(NotificationType type, |
| const NotificationSource& source, |
| const NotificationDetails& details) { |
| DCHECK(type == NotificationType::BOOKMARK_MODEL_LOADED); |
| DCHECK(profile()->GetBookmarkModel()->IsLoaded()); |
| Run(); |
| Release(); // Balanced in Run(). |
| } |
| |
| // static |
| ExtensionBookmarkEventRouter* ExtensionBookmarkEventRouter::GetInstance() { |
| return Singleton<ExtensionBookmarkEventRouter>::get(); |
| } |
| |
| ExtensionBookmarkEventRouter::ExtensionBookmarkEventRouter() { |
| } |
| |
| ExtensionBookmarkEventRouter::~ExtensionBookmarkEventRouter() { |
| } |
| |
| void ExtensionBookmarkEventRouter::Observe(BookmarkModel* model) { |
| if (models_.find(model) == models_.end()) { |
| model->AddObserver(this); |
| models_.insert(model); |
| } |
| } |
| |
| void ExtensionBookmarkEventRouter::DispatchEvent(Profile *profile, |
| const char* event_name, |
| const std::string json_args) { |
| if (profile->GetExtensionEventRouter()) { |
| profile->GetExtensionEventRouter()->DispatchEventToRenderers( |
| event_name, json_args, NULL, GURL()); |
| } |
| } |
| |
| void ExtensionBookmarkEventRouter::Loaded(BookmarkModel* model) { |
| // TODO(erikkay): Perhaps we should send this event down to the extension |
| // so they know when it's safe to use the API? |
| } |
| |
| void ExtensionBookmarkEventRouter::BookmarkNodeMoved( |
| BookmarkModel* model, |
| const BookmarkNode* old_parent, |
| int old_index, |
| const BookmarkNode* new_parent, |
| int new_index) { |
| ListValue args; |
| const BookmarkNode* node = new_parent->GetChild(new_index); |
| args.Append(new StringValue(base::Int64ToString(node->id()))); |
| DictionaryValue* object_args = new DictionaryValue(); |
| object_args->SetString(keys::kParentIdKey, |
| base::Int64ToString(new_parent->id())); |
| object_args->SetInteger(keys::kIndexKey, new_index); |
| object_args->SetString(keys::kOldParentIdKey, |
| base::Int64ToString(old_parent->id())); |
| object_args->SetInteger(keys::kOldIndexKey, old_index); |
| args.Append(object_args); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| DispatchEvent(model->profile(), keys::kOnBookmarkMoved, json_args); |
| } |
| |
| void ExtensionBookmarkEventRouter::BookmarkNodeAdded(BookmarkModel* model, |
| const BookmarkNode* parent, |
| int index) { |
| ListValue args; |
| const BookmarkNode* node = parent->GetChild(index); |
| args.Append(new StringValue(base::Int64ToString(node->id()))); |
| DictionaryValue* obj = |
| extension_bookmark_helpers::GetNodeDictionary(node, false, false); |
| args.Append(obj); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| DispatchEvent(model->profile(), keys::kOnBookmarkCreated, json_args); |
| } |
| |
| void ExtensionBookmarkEventRouter::BookmarkNodeRemoved( |
| BookmarkModel* model, |
| const BookmarkNode* parent, |
| int index, |
| const BookmarkNode* node) { |
| ListValue args; |
| args.Append(new StringValue(base::Int64ToString(node->id()))); |
| DictionaryValue* object_args = new DictionaryValue(); |
| object_args->SetString(keys::kParentIdKey, |
| base::Int64ToString(parent->id())); |
| object_args->SetInteger(keys::kIndexKey, index); |
| args.Append(object_args); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| DispatchEvent(model->profile(), keys::kOnBookmarkRemoved, json_args); |
| } |
| |
| void ExtensionBookmarkEventRouter::BookmarkNodeChanged( |
| BookmarkModel* model, const BookmarkNode* node) { |
| ListValue args; |
| args.Append(new StringValue(base::Int64ToString(node->id()))); |
| |
| // TODO(erikkay) The only three things that BookmarkModel sends this |
| // notification for are title, url and favicon. Since we're currently |
| // ignoring favicon and since the notification doesn't say which one anyway, |
| // for now we only include title and url. The ideal thing would be to change |
| // BookmarkModel to indicate what changed. |
| DictionaryValue* object_args = new DictionaryValue(); |
| object_args->SetString(keys::kTitleKey, node->GetTitle()); |
| if (node->is_url()) |
| object_args->SetString(keys::kUrlKey, node->GetURL().spec()); |
| args.Append(object_args); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| DispatchEvent(model->profile(), keys::kOnBookmarkChanged, json_args); |
| } |
| |
| void ExtensionBookmarkEventRouter::BookmarkNodeFavIconLoaded( |
| BookmarkModel* model, const BookmarkNode* node) { |
| // TODO(erikkay) anything we should do here? |
| } |
| |
| void ExtensionBookmarkEventRouter::BookmarkNodeChildrenReordered( |
| BookmarkModel* model, const BookmarkNode* node) { |
| ListValue args; |
| args.Append(new StringValue(base::Int64ToString(node->id()))); |
| int childCount = node->GetChildCount(); |
| ListValue* children = new ListValue(); |
| for (int i = 0; i < childCount; ++i) { |
| const BookmarkNode* child = node->GetChild(i); |
| Value* child_id = new StringValue(base::Int64ToString(child->id())); |
| children->Append(child_id); |
| } |
| DictionaryValue* reorder_info = new DictionaryValue(); |
| reorder_info->Set(keys::kChildIdsKey, children); |
| args.Append(reorder_info); |
| |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| DispatchEvent(model->profile(), |
| keys::kOnBookmarkChildrenReordered, |
| json_args); |
| } |
| |
| void ExtensionBookmarkEventRouter:: |
| BookmarkImportBeginning(BookmarkModel* model) { |
| ListValue args; |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| DispatchEvent(model->profile(), |
| keys::kOnBookmarkImportBegan, |
| json_args); |
| } |
| |
| void ExtensionBookmarkEventRouter::BookmarkImportEnding(BookmarkModel* model) { |
| ListValue args; |
| std::string json_args; |
| base::JSONWriter::Write(&args, false, &json_args); |
| DispatchEvent(model->profile(), |
| keys::kOnBookmarkImportEnded, |
| json_args); |
| } |
| |
| bool GetBookmarksFunction::RunImpl() { |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| scoped_ptr<ListValue> json(new ListValue()); |
| Value* arg0; |
| EXTENSION_FUNCTION_VALIDATE(args_->Get(0, &arg0)); |
| if (arg0->IsType(Value::TYPE_LIST)) { |
| const ListValue* ids = static_cast<const ListValue*>(arg0); |
| size_t count = ids->GetSize(); |
| EXTENSION_FUNCTION_VALIDATE(count > 0); |
| for (size_t i = 0; i < count; ++i) { |
| int64 id; |
| std::string id_string; |
| EXTENSION_FUNCTION_VALIDATE(ids->GetString(i, &id_string)); |
| if (!GetBookmarkIdAsInt64(id_string, &id)) |
| return false; |
| const BookmarkNode* node = model->GetNodeByID(id); |
| if (!node) { |
| error_ = keys::kNoNodeError; |
| return false; |
| } else { |
| extension_bookmark_helpers::AddNode(node, json.get(), false); |
| } |
| } |
| } else { |
| int64 id; |
| std::string id_string; |
| EXTENSION_FUNCTION_VALIDATE(arg0->GetAsString(&id_string)); |
| if (!GetBookmarkIdAsInt64(id_string, &id)) |
| return false; |
| const BookmarkNode* node = model->GetNodeByID(id); |
| if (!node) { |
| error_ = keys::kNoNodeError; |
| return false; |
| } |
| extension_bookmark_helpers::AddNode(node, json.get(), false); |
| } |
| |
| result_.reset(json.release()); |
| return true; |
| } |
| |
| bool GetBookmarkChildrenFunction::RunImpl() { |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| int64 id; |
| std::string id_string; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &id_string)); |
| if (!GetBookmarkIdAsInt64(id_string, &id)) |
| return false; |
| scoped_ptr<ListValue> json(new ListValue()); |
| const BookmarkNode* node = model->GetNodeByID(id); |
| if (!node) { |
| error_ = keys::kNoNodeError; |
| return false; |
| } |
| int child_count = node->GetChildCount(); |
| for (int i = 0; i < child_count; ++i) { |
| const BookmarkNode* child = node->GetChild(i); |
| extension_bookmark_helpers::AddNode(child, json.get(), false); |
| } |
| |
| result_.reset(json.release()); |
| return true; |
| } |
| |
| bool GetBookmarkRecentFunction::RunImpl() { |
| int number_of_items; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetInteger(0, &number_of_items)); |
| if (number_of_items < 1) |
| return false; |
| |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| ListValue* json = new ListValue(); |
| std::vector<const BookmarkNode*> nodes; |
| bookmark_utils::GetMostRecentlyAddedEntries(model, number_of_items, &nodes); |
| std::vector<const BookmarkNode*>::iterator i = nodes.begin(); |
| for (; i != nodes.end(); ++i) { |
| const BookmarkNode* node = *i; |
| extension_bookmark_helpers::AddNode(node, json, false); |
| } |
| result_.reset(json); |
| return true; |
| } |
| |
| bool GetBookmarkTreeFunction::RunImpl() { |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| scoped_ptr<ListValue> json(new ListValue()); |
| const BookmarkNode* node = model->root_node(); |
| extension_bookmark_helpers::AddNode(node, json.get(), true); |
| result_.reset(json.release()); |
| return true; |
| } |
| |
| bool SearchBookmarksFunction::RunImpl() { |
| string16 query; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &query)); |
| |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| ListValue* json = new ListValue(); |
| std::string lang = profile()->GetPrefs()->GetString(prefs::kAcceptLanguages); |
| std::vector<const BookmarkNode*> nodes; |
| bookmark_utils::GetBookmarksContainingText(model, query, |
| std::numeric_limits<int>::max(), |
| lang, &nodes); |
| std::vector<const BookmarkNode*>::iterator i = nodes.begin(); |
| for (; i != nodes.end(); ++i) { |
| const BookmarkNode* node = *i; |
| extension_bookmark_helpers::AddNode(node, json, false); |
| } |
| |
| result_.reset(json); |
| return true; |
| } |
| |
| // static |
| bool RemoveBookmarkFunction::ExtractIds(const ListValue* args, |
| std::list<int64>* ids, |
| bool* invalid_id) { |
| std::string id_string; |
| if (!args->GetString(0, &id_string)) |
| return false; |
| int64 id; |
| if (base::StringToInt64(id_string, &id)) |
| ids->push_back(id); |
| else |
| *invalid_id = true; |
| return true; |
| } |
| |
| bool RemoveBookmarkFunction::RunImpl() { |
| std::list<int64> ids; |
| bool invalid_id = false; |
| EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id)); |
| if (invalid_id) { |
| error_ = keys::kInvalidIdError; |
| return false; |
| } |
| bool recursive = false; |
| if (name() == RemoveTreeBookmarkFunction::function_name()) |
| recursive = true; |
| |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| size_t count = ids.size(); |
| EXTENSION_FUNCTION_VALIDATE(count > 0); |
| for (std::list<int64>::iterator it = ids.begin(); it != ids.end(); ++it) { |
| if (!extension_bookmark_helpers::RemoveNode(model, *it, recursive, &error_)) |
| return false; |
| } |
| return true; |
| } |
| |
| bool CreateBookmarkFunction::RunImpl() { |
| DictionaryValue* json; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(0, &json)); |
| EXTENSION_FUNCTION_VALIDATE(json != NULL); |
| |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| int64 parentId; |
| if (!json->HasKey(keys::kParentIdKey)) { |
| // Optional, default to "other bookmarks". |
| parentId = model->other_node()->id(); |
| } else { |
| std::string parentId_string; |
| EXTENSION_FUNCTION_VALIDATE(json->GetString(keys::kParentIdKey, |
| &parentId_string)); |
| if (!GetBookmarkIdAsInt64(parentId_string, &parentId)) |
| return false; |
| } |
| const BookmarkNode* parent = model->GetNodeByID(parentId); |
| if (!parent) { |
| error_ = keys::kNoParentError; |
| return false; |
| } |
| if (parent->GetParent() == NULL) { // Can't create children of the root. |
| error_ = keys::kNoParentError; |
| return false; |
| } |
| |
| int index; |
| if (!json->HasKey(keys::kIndexKey)) { // Optional (defaults to end). |
| index = parent->GetChildCount(); |
| } else { |
| EXTENSION_FUNCTION_VALIDATE(json->GetInteger(keys::kIndexKey, &index)); |
| if (index > parent->GetChildCount() || index < 0) { |
| error_ = keys::kInvalidIndexError; |
| return false; |
| } |
| } |
| |
| string16 title; |
| json->GetString(keys::kTitleKey, &title); // Optional. |
| std::string url_string; |
| json->GetString(keys::kUrlKey, &url_string); // Optional. |
| GURL url(url_string); |
| if (!url.is_empty() && !url.is_valid()) { |
| error_ = keys::kInvalidUrlError; |
| return false; |
| } |
| |
| const BookmarkNode* node; |
| if (url_string.length()) |
| node = model->AddURL(parent, index, title, url); |
| else |
| node = model->AddGroup(parent, index, title); |
| DCHECK(node); |
| if (!node) { |
| error_ = keys::kNoNodeError; |
| return false; |
| } |
| |
| DictionaryValue* ret = |
| extension_bookmark_helpers::GetNodeDictionary(node, false, false); |
| result_.reset(ret); |
| |
| return true; |
| } |
| |
| // static |
| bool MoveBookmarkFunction::ExtractIds(const ListValue* args, |
| std::list<int64>* ids, |
| bool* invalid_id) { |
| // For now, Move accepts ID parameters in the same way as an Update. |
| return UpdateBookmarkFunction::ExtractIds(args, ids, invalid_id); |
| } |
| |
| bool MoveBookmarkFunction::RunImpl() { |
| std::list<int64> ids; |
| bool invalid_id = false; |
| EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id)); |
| if (invalid_id) { |
| error_ = keys::kInvalidIdError; |
| return false; |
| } |
| EXTENSION_FUNCTION_VALIDATE(ids.size() == 1); |
| |
| DictionaryValue* destination; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &destination)); |
| |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| const BookmarkNode* node = model->GetNodeByID(ids.front()); |
| if (!node) { |
| error_ = keys::kNoNodeError; |
| return false; |
| } |
| if (node == model->root_node() || |
| node == model->other_node() || |
| node == model->GetBookmarkBarNode()) { |
| error_ = keys::kModifySpecialError; |
| return false; |
| } |
| |
| const BookmarkNode* parent = NULL; |
| if (!destination->HasKey(keys::kParentIdKey)) { |
| // Optional, defaults to current parent. |
| parent = node->GetParent(); |
| } else { |
| std::string parentId_string; |
| EXTENSION_FUNCTION_VALIDATE(destination->GetString(keys::kParentIdKey, |
| &parentId_string)); |
| int64 parentId; |
| if (!GetBookmarkIdAsInt64(parentId_string, &parentId)) |
| return false; |
| |
| parent = model->GetNodeByID(parentId); |
| } |
| if (!parent) { |
| error_ = keys::kNoParentError; |
| // TODO(erikkay) return an error message. |
| return false; |
| } |
| if (parent == model->root_node()) { |
| error_ = keys::kModifySpecialError; |
| return false; |
| } |
| |
| int index; |
| if (destination->HasKey(keys::kIndexKey)) { // Optional (defaults to end). |
| EXTENSION_FUNCTION_VALIDATE(destination->GetInteger(keys::kIndexKey, |
| &index)); |
| if (index > parent->GetChildCount() || index < 0) { |
| error_ = keys::kInvalidIndexError; |
| return false; |
| } |
| } else { |
| index = parent->GetChildCount(); |
| } |
| |
| model->Move(node, parent, index); |
| |
| DictionaryValue* ret = |
| extension_bookmark_helpers::GetNodeDictionary(node, false, false); |
| result_.reset(ret); |
| |
| return true; |
| } |
| |
| // static |
| bool UpdateBookmarkFunction::ExtractIds(const ListValue* args, |
| std::list<int64>* ids, |
| bool* invalid_id) { |
| // For now, Update accepts ID parameters in the same way as an Remove. |
| return RemoveBookmarkFunction::ExtractIds(args, ids, invalid_id); |
| } |
| |
| bool UpdateBookmarkFunction::RunImpl() { |
| std::list<int64> ids; |
| bool invalid_id = false; |
| EXTENSION_FUNCTION_VALIDATE(ExtractIds(args_.get(), &ids, &invalid_id)); |
| if (invalid_id) { |
| error_ = keys::kInvalidIdError; |
| return false; |
| } |
| EXTENSION_FUNCTION_VALIDATE(ids.size() == 1); |
| |
| DictionaryValue* updates; |
| EXTENSION_FUNCTION_VALIDATE(args_->GetDictionary(1, &updates)); |
| |
| // Optional but we need to distinguish non present from an empty title. |
| string16 title; |
| const bool has_title = updates->GetString(keys::kTitleKey, &title); |
| |
| // Optional. |
| std::string url_string; |
| updates->GetString(keys::kUrlKey, &url_string); |
| GURL url(url_string); |
| if (!url_string.empty() && !url.is_valid()) { |
| error_ = keys::kInvalidUrlError; |
| return false; |
| } |
| |
| BookmarkModel* model = profile()->GetBookmarkModel(); |
| const BookmarkNode* node = model->GetNodeByID(ids.front()); |
| if (!node) { |
| error_ = keys::kNoNodeError; |
| return false; |
| } |
| if (node == model->root_node() || |
| node == model->other_node() || |
| node == model->GetBookmarkBarNode()) { |
| error_ = keys::kModifySpecialError; |
| return false; |
| } |
| if (has_title) |
| model->SetTitle(node, title); |
| if (!url.is_empty()) |
| model->SetURL(node, url); |
| |
| DictionaryValue* ret = |
| extension_bookmark_helpers::GetNodeDictionary(node, false, false); |
| result_.reset(ret); |
| |
| return true; |
| } |
| |
| // Mapper superclass for BookmarkFunctions. |
| template <typename BucketIdType> |
| class BookmarkBucketMapper : public BucketMapper { |
| public: |
| virtual ~BookmarkBucketMapper() { STLDeleteValues(&buckets_); } |
| protected: |
| Bucket* GetBucket(const BucketIdType& id) { |
| Bucket* b = buckets_[id]; |
| if (b == NULL) { |
| b = new Bucket(); |
| buckets_[id] = b; |
| } |
| return b; |
| } |
| private: |
| std::map<BucketIdType, Bucket*> buckets_; |
| }; |
| |
| // Mapper for 'bookmarks.create'. Maps "same input to bookmarks.create" to a |
| // unique bucket. |
| class CreateBookmarkBucketMapper : public BookmarkBucketMapper<std::string> { |
| public: |
| explicit CreateBookmarkBucketMapper(Profile* profile) : profile_(profile) {} |
| // TODO(tim): This should share code with CreateBookmarkFunction::RunImpl, |
| // but I can't figure out a good way to do that with all the macros. |
| virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) { |
| DictionaryValue* json; |
| if (!args->GetDictionary(0, &json)) |
| return; |
| |
| std::string parent_id; |
| if (json->HasKey(keys::kParentIdKey)) { |
| if (!json->GetString(keys::kParentIdKey, &parent_id)) |
| return; |
| } |
| BookmarkModel* model = profile_->GetBookmarkModel(); |
| |
| int64 parent_id_int64; |
| base::StringToInt64(parent_id, &parent_id_int64); |
| const BookmarkNode* parent = model->GetNodeByID(parent_id_int64); |
| if (!parent) |
| return; |
| |
| std::string bucket_id = UTF16ToUTF8(parent->GetTitle()); |
| std::string title; |
| json->GetString(keys::kTitleKey, &title); |
| std::string url_string; |
| json->GetString(keys::kUrlKey, &url_string); |
| |
| bucket_id += title; |
| bucket_id += url_string; |
| // 20 bytes (SHA1 hash length) is very likely less than most of the |
| // |bucket_id| strings we construct here, so we hash it to save space. |
| buckets->push_back(GetBucket(base::SHA1HashString(bucket_id))); |
| } |
| private: |
| Profile* profile_; |
| }; |
| |
| // Mapper for 'bookmarks.remove'. |
| class RemoveBookmarksBucketMapper : public BookmarkBucketMapper<std::string> { |
| public: |
| explicit RemoveBookmarksBucketMapper(Profile* profile) : profile_(profile) {} |
| virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) { |
| typedef std::list<int64> IdList; |
| IdList ids; |
| bool invalid_id = false; |
| if (!RemoveBookmarkFunction::ExtractIds(args, &ids, &invalid_id) || |
| invalid_id) { |
| return; |
| } |
| |
| for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) { |
| BookmarkModel* model = profile_->GetBookmarkModel(); |
| const BookmarkNode* node = model->GetNodeByID(*it); |
| if (!node || !node->GetParent()) |
| return; |
| |
| std::string bucket_id; |
| bucket_id += UTF16ToUTF8(node->GetParent()->GetTitle()); |
| bucket_id += UTF16ToUTF8(node->GetTitle()); |
| bucket_id += node->GetURL().spec(); |
| buckets->push_back(GetBucket(base::SHA1HashString(bucket_id))); |
| } |
| } |
| private: |
| Profile* profile_; |
| }; |
| |
| // Mapper for any bookmark function accepting bookmark IDs as parameters, where |
| // a distinct ID corresponds to a single item in terms of quota limiting. This |
| // is inappropriate for bookmarks.remove, for example, since repeated removals |
| // of the same item will actually have a different ID each time. |
| template <class FunctionType> |
| class BookmarkIdMapper : public BookmarkBucketMapper<int64> { |
| public: |
| typedef std::list<int64> IdList; |
| virtual void GetBucketsForArgs(const ListValue* args, BucketList* buckets) { |
| IdList ids; |
| bool invalid_id = false; |
| if (!FunctionType::ExtractIds(args, &ids, &invalid_id) || invalid_id) |
| return; |
| for (IdList::iterator it = ids.begin(); it != ids.end(); ++it) |
| buckets->push_back(GetBucket(*it)); |
| } |
| }; |
| |
| // Builds heuristics for all BookmarkFunctions using specialized BucketMappers. |
| class BookmarksQuotaLimitFactory { |
| public: |
| // For id-based bookmark functions. |
| template <class FunctionType> |
| static void Build(QuotaLimitHeuristics* heuristics) { |
| BuildWithMappers(heuristics, new BookmarkIdMapper<FunctionType>(), |
| new BookmarkIdMapper<FunctionType>()); |
| } |
| |
| // For bookmarks.create. |
| static void BuildForCreate(QuotaLimitHeuristics* heuristics, |
| Profile* profile) { |
| BuildWithMappers(heuristics, new CreateBookmarkBucketMapper(profile), |
| new CreateBookmarkBucketMapper(profile)); |
| } |
| |
| // For bookmarks.remove. |
| static void BuildForRemove(QuotaLimitHeuristics* heuristics, |
| Profile* profile) { |
| BuildWithMappers(heuristics, new RemoveBookmarksBucketMapper(profile), |
| new RemoveBookmarksBucketMapper(profile)); |
| } |
| |
| private: |
| static void BuildWithMappers(QuotaLimitHeuristics* heuristics, |
| BucketMapper* short_mapper, BucketMapper* long_mapper) { |
| TimedLimit* timed = new TimedLimit(kLongLimitConfig, long_mapper); |
| // A max of two operations per minute, sustained over 10 minutes. |
| SustainedLimit* sustained = new SustainedLimit(TimeDelta::FromMinutes(10), |
| kShortLimitConfig, short_mapper); |
| heuristics->push_back(timed); |
| heuristics->push_back(sustained); |
| } |
| |
| // The quota configurations used for all BookmarkFunctions. |
| static const Config kShortLimitConfig; |
| static const Config kLongLimitConfig; |
| |
| DISALLOW_IMPLICIT_CONSTRUCTORS(BookmarksQuotaLimitFactory); |
| }; |
| |
| const Config BookmarksQuotaLimitFactory::kShortLimitConfig = { |
| 2, // 2 tokens per interval. |
| TimeDelta::FromMinutes(1) // 1 minute long refill interval. |
| }; |
| |
| const Config BookmarksQuotaLimitFactory::kLongLimitConfig = { |
| 100, // 100 tokens per interval. |
| TimeDelta::FromHours(1) // 1 hour long refill interval. |
| }; |
| |
| // And finally, building the individual heuristics for each function. |
| void RemoveBookmarkFunction::GetQuotaLimitHeuristics( |
| QuotaLimitHeuristics* heuristics) const { |
| BookmarksQuotaLimitFactory::BuildForRemove(heuristics, profile()); |
| } |
| |
| void MoveBookmarkFunction::GetQuotaLimitHeuristics( |
| QuotaLimitHeuristics* heuristics) const { |
| BookmarksQuotaLimitFactory::Build<MoveBookmarkFunction>(heuristics); |
| } |
| |
| void UpdateBookmarkFunction::GetQuotaLimitHeuristics( |
| QuotaLimitHeuristics* heuristics) const { |
| BookmarksQuotaLimitFactory::Build<UpdateBookmarkFunction>(heuristics); |
| }; |
| |
| void CreateBookmarkFunction::GetQuotaLimitHeuristics( |
| QuotaLimitHeuristics* heuristics) const { |
| BookmarksQuotaLimitFactory::BuildForCreate(heuristics, profile()); |
| } |
| |
| BookmarksIOFunction::BookmarksIOFunction() {} |
| |
| BookmarksIOFunction::~BookmarksIOFunction() {} |
| |
| void BookmarksIOFunction::SelectFile(SelectFileDialog::Type type) { |
| // Balanced in one of the three callbacks of SelectFileDialog: |
| // either FileSelectionCanceled, MultiFilesSelected, or FileSelected |
| AddRef(); |
| select_file_dialog_ = SelectFileDialog::Create(this); |
| SelectFileDialog::FileTypeInfo file_type_info; |
| file_type_info.extensions.resize(1); |
| file_type_info.extensions[0].push_back(FILE_PATH_LITERAL("html")); |
| |
| select_file_dialog_->SelectFile(type, |
| string16(), |
| FilePath(), |
| &file_type_info, |
| 0, |
| FILE_PATH_LITERAL(""), |
| NULL, |
| NULL); |
| } |
| |
| void BookmarksIOFunction::FileSelectionCanceled(void* params) { |
| Release(); // Balanced in BookmarksIOFunction::SelectFile() |
| } |
| |
| void BookmarksIOFunction::MultiFilesSelected( |
| const std::vector<FilePath>& files, void* params) { |
| Release(); // Balanced in BookmarsIOFunction::SelectFile() |
| NOTREACHED() << "Should not be able to select multiple files"; |
| } |
| |
| bool ImportBookmarksFunction::RunImpl() { |
| SelectFile(SelectFileDialog::SELECT_OPEN_FILE); |
| return true; |
| } |
| |
| void ImportBookmarksFunction::FileSelected(const FilePath& path, |
| int index, |
| void* params) { |
| ImporterHost* host = new ImporterHost(); |
| importer::ProfileInfo profile_info; |
| profile_info.browser_type = importer::BOOKMARKS_HTML; |
| profile_info.source_path = path; |
| host->StartImportSettings(profile_info, |
| profile(), |
| importer::FAVORITES, |
| new ProfileWriter(profile()), |
| true); |
| Release(); // Balanced in BookmarksIOFunction::SelectFile() |
| } |
| |
| bool ExportBookmarksFunction::RunImpl() { |
| SelectFile(SelectFileDialog::SELECT_SAVEAS_FILE); |
| return true; |
| } |
| |
| void ExportBookmarksFunction::FileSelected(const FilePath& path, |
| int index, |
| void* params) { |
| bookmark_html_writer::WriteBookmarks(profile(), path, NULL); |
| Release(); // Balanced in BookmarksIOFunction::SelectFile() |
| } |