diff options
author | noyau <noyau@chromium.org> | 2014-09-12 03:19:39 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-09-12 10:21:16 +0000 |
commit | f0e1e779e3acf7e18093550dc775e44b812a3286 (patch) | |
tree | 7e876cfc585e2125e3730162e7e30018ab5e6ab2 /components/enhanced_bookmarks | |
parent | fa32f2e56ac125ff2c95f35473f123a8d2bc2012 (diff) | |
download | chromium_src-f0e1e779e3acf7e18093550dc775e44b812a3286.zip chromium_src-f0e1e779e3acf7e18093550dc775e44b812a3286.tar.gz chromium_src-f0e1e779e3acf7e18093550dc775e44b812a3286.tar.bz2 |
Bring up of the server side full text search.
This class is used to perform a full text search on bookmarks, powered by the server side. The superclass will be used for other services in subsequent CLs.
BUG=None
Review URL: https://codereview.chromium.org/538903003
Cr-Commit-Position: refs/heads/master@{#294571}
Diffstat (limited to 'components/enhanced_bookmarks')
-rw-r--r-- | components/enhanced_bookmarks/DEPS | 2 | ||||
-rw-r--r-- | components/enhanced_bookmarks/bookmark_server_search_service.cc | 115 | ||||
-rw-r--r-- | components/enhanced_bookmarks/bookmark_server_search_service.h | 63 | ||||
-rw-r--r-- | components/enhanced_bookmarks/bookmark_server_service.cc | 235 | ||||
-rw-r--r-- | components/enhanced_bookmarks/bookmark_server_service.h | 156 | ||||
-rw-r--r-- | components/enhanced_bookmarks/proto/BUILD.gn | 1 | ||||
-rw-r--r-- | components/enhanced_bookmarks/proto/search.proto | 30 |
7 files changed, 602 insertions, 0 deletions
diff --git a/components/enhanced_bookmarks/DEPS b/components/enhanced_bookmarks/DEPS index 0d0fb7d..5383ad5 100644 --- a/components/enhanced_bookmarks/DEPS +++ b/components/enhanced_bookmarks/DEPS @@ -1,6 +1,8 @@ include_rules = [ "+components/bookmarks", "+components/keyed_service", + "+components/signin", + "+google_apis/gaia", "+jni", "+net", "+sql", diff --git a/components/enhanced_bookmarks/bookmark_server_search_service.cc b/components/enhanced_bookmarks/bookmark_server_search_service.cc new file mode 100644 index 0000000..642d5c8 --- /dev/null +++ b/components/enhanced_bookmarks/bookmark_server_search_service.cc @@ -0,0 +1,115 @@ +// Copyright 2014 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 "components/enhanced_bookmarks/bookmark_server_search_service.h" + +#include "components/bookmarks/browser/bookmark_model.h" +#include "components/enhanced_bookmarks/enhanced_bookmark_utils.h" +#include "components/enhanced_bookmarks/proto/search.pb.h" +#include "net/base/url_util.h" +#include "net/url_request/url_fetcher.h" + +namespace { +const std::string kSearchUrl( + "https://www.google.com/stars/search"); +} // namespace + +namespace enhanced_bookmarks { + +BookmarkServerSearchService::BookmarkServerSearchService( + scoped_refptr<net::URLRequestContextGetter> request_context_getter, + ProfileOAuth2TokenService* token_service, + SigninManagerBase* signin_manager, + BookmarkModel* bookmark_model) + : BookmarkServerService(request_context_getter, + token_service, + signin_manager, + bookmark_model) { +} + +BookmarkServerSearchService::~BookmarkServerSearchService() { +} + +void BookmarkServerSearchService::Search(const std::string& query) { + DCHECK(query.length()); + current_query_ = query; + TriggerTokenRequest(true); +} + +std::vector<const BookmarkNode*> BookmarkServerSearchService::ResultForQuery( + const std::string& query) { + DCHECK(query.length()); + std::vector<const BookmarkNode*> result; + + std::map<std::string, std::vector<std::string> >::iterator it = + searches_.find(query); + if (it == searches_.end()) + return result; + + for (std::vector<std::string>::iterator clip_it = it->second.begin(); + clip_it != it->second.end(); + ++clip_it) { + const BookmarkNode* node = BookmarkForRemoteId(*clip_it); + if (node) + result.push_back(node); + } + return result; +} + +net::URLFetcher* BookmarkServerSearchService::CreateFetcher() { + // Add the necessary arguments to the URI. + GURL url(kSearchUrl); + url = net::AppendQueryParameter(url, "output", "proto"); + url = net::AppendQueryParameter(url, "q", current_query_); + + // Build the URLFetcher to perform the request. + net::URLFetcher* url_fetcher = + net::URLFetcher::Create(url, net::URLFetcher::GET, this); + + return url_fetcher; +} + +bool BookmarkServerSearchService::ProcessResponse(const std::string& response, + bool* should_notify) { + DCHECK(*should_notify); + DCHECK(current_query_.length()); + image::collections::CorpusSearchResult response_proto; + bool result = response_proto.ParseFromString(response); + if (!result) + return false; // Not formatted properly. + + std::vector<std::string> clip_ids; + for (google::protobuf::RepeatedPtrField< + image::collections::CorpusSearchResult_ClipResult>::const_iterator + it = response_proto.results().begin(); + it != response_proto.results().end(); + ++it) { + const std::string& clip_id = it->clip_id(); + if (!clip_id.length()) + continue; + clip_ids.push_back(clip_id); + } + searches_[current_query_] = clip_ids; + current_query_.clear(); + return true; +} + +void BookmarkServerSearchService::CleanAfterFailure() { + searches_.clear(); +} + +void BookmarkServerSearchService::BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) { + BookmarkServerService::BookmarkNodeAdded(model, parent, index); + searches_.clear(); +} + +void BookmarkServerSearchService::BookmarkMetaInfoChanged( + BookmarkModel* model, + const BookmarkNode* node) { + BookmarkServerService::BookmarkMetaInfoChanged(model, node); + searches_.clear(); +} +} // namespace enhanced_bookmarks diff --git a/components/enhanced_bookmarks/bookmark_server_search_service.h b/components/enhanced_bookmarks/bookmark_server_search_service.h new file mode 100644 index 0000000..934458e --- /dev/null +++ b/components/enhanced_bookmarks/bookmark_server_search_service.h @@ -0,0 +1,63 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_ENHANCED_BOOKMARKS_BOOKMARK_SERVER_SEARCH_SERVICE_H_ +#define COMPONENTS_ENHANCED_BOOKMARKS_BOOKMARK_SERVER_SEARCH_SERVICE_H_ + +#include <string> +#include <vector> + +#include "components/enhanced_bookmarks/bookmark_server_service.h" +#include "net/url_request/url_fetcher.h" + +class BookmarkModel; + +namespace enhanced_bookmarks { + +// Sends requests to the bookmark server to search for bookmarks relevant to a +// given query. Will handle one outgoing request at a time. +class BookmarkServerSearchService : public BookmarkServerService { + public: + BookmarkServerSearchService( + scoped_refptr<net::URLRequestContextGetter> request_context_getter, + ProfileOAuth2TokenService* token_service, + SigninManagerBase* signin_manager, + BookmarkModel* bookmark_model); + virtual ~BookmarkServerSearchService(); + + // Triggers a search. The query must not be empty. A call to this method + // cancels any previous searches. OnChange() is garanteed to be called once + // per query. + void Search(const std::string& query); + + // Returns the search results. The results are only available after the + // OnChange() observer methods has been sent. This method will return an empty + // result otherwise. query should be a string passed to Search() previously. + std::vector<const BookmarkNode*> ResultForQuery(const std::string& query); + + protected: + + virtual net::URLFetcher* CreateFetcher() OVERRIDE; + + virtual bool ProcessResponse(const std::string& response, + bool* should_notify) OVERRIDE; + + virtual void CleanAfterFailure() OVERRIDE; + + // BookmarkModelObserver methods. + virtual void BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) OVERRIDE; + virtual void BookmarkMetaInfoChanged(BookmarkModel* model, + const BookmarkNode* node) OVERRIDE; + + private: + // The search data, a map from query to a vector of stars.id. + std::map<std::string, std::vector<std::string> > searches_; + std::string current_query_; + DISALLOW_COPY_AND_ASSIGN(BookmarkServerSearchService); +}; +} // namespace enhanced_bookmarks + +#endif // COMPONENTS_ENHANCED_BOOKMARKS_BOOKMARK_SERVER_SEARCH_SERVICE_H_ diff --git a/components/enhanced_bookmarks/bookmark_server_service.cc b/components/enhanced_bookmarks/bookmark_server_service.cc new file mode 100644 index 0000000..a5b0e37 --- /dev/null +++ b/components/enhanced_bookmarks/bookmark_server_service.cc @@ -0,0 +1,235 @@ +// Copyright 2014 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 "components/enhanced_bookmarks/bookmark_server_service.h" + +#include "base/auto_reset.h" +#include "components/bookmarks/browser/bookmark_model.h" +#include "components/bookmarks/browser/bookmark_model_observer.h" +#include "components/enhanced_bookmarks/metadata_accessor.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" +#include "components/signin/core/browser/signin_manager_base.h" +#include "google_apis/gaia/gaia_constants.h" +#include "net/base/load_flags.h" +#include "net/url_request/url_request_context_getter.h" +#include "ui/base/models/tree_node_iterator.h" + +namespace enhanced_bookmarks { + +BookmarkServerService::BookmarkServerService( + scoped_refptr<net::URLRequestContextGetter> request_context_getter, + ProfileOAuth2TokenService* token_service, + SigninManagerBase* signin_manager, + BookmarkModel* bookmark_model) + : OAuth2TokenService::Consumer("bookmark_server_service"), + bookmark_model_(bookmark_model), + token_service_(token_service), + signin_manager_(signin_manager), + request_context_getter_(request_context_getter), + inhibit_change_notifications_(false) { + DCHECK(request_context_getter.get()); + DCHECK(token_service); + DCHECK(signin_manager); + DCHECK(bookmark_model); + bookmark_model_->AddObserver(this); + if (bookmark_model_->loaded()) + BuildIdMap(); +} + +BookmarkServerService::~BookmarkServerService() { + bookmark_model_->RemoveObserver(this); +} + +void BookmarkServerService::AddObserver( + BookmarkServerServiceObserver* observer) { + observers_.AddObserver(observer); +} + +void BookmarkServerService::RemoveObserver( + BookmarkServerServiceObserver* observer) { + observers_.RemoveObserver(observer); +} + +void BookmarkServerService::BuildIdMap() { + ui::TreeNodeIterator<const BookmarkNode> iterator( + bookmark_model_->root_node()); + + while (iterator.has_next()) { + const BookmarkNode* bookmark = iterator.Next(); + if (bookmark_model_->is_permanent_node(bookmark)) + continue; + // RemoteIdFromBookmark() will create the ID if it doesn't exists yet. + std::string starid = + enhanced_bookmarks::RemoteIdFromBookmark(bookmark_model_, bookmark); + if (bookmark->is_url()) { + starsid_to_bookmark_[starid] = bookmark; + } + } +} + +const BookmarkNode* BookmarkServerService::BookmarkForRemoteId( + const std::string& remote_id) const { + std::map<std::string, const BookmarkNode*>::const_iterator it = + starsid_to_bookmark_.find(remote_id); + if (it == starsid_to_bookmark_.end()) + return NULL; + return it->second; +} + +const std::string BookmarkServerService::RemoteIDForBookmark( + const BookmarkNode* bookmark) const { + return enhanced_bookmarks::RemoteIdFromBookmark(bookmark_model_, bookmark); +} + +void BookmarkServerService::Notify() { + FOR_EACH_OBSERVER(BookmarkServerServiceObserver, observers_, OnChange(this)); +} + +void BookmarkServerService::TriggerTokenRequest(bool cancel_previous) { + if (cancel_previous) + url_fetcher_.reset(); + + if (token_request_ || url_fetcher_) + return; // Fetcher is already running. + + const std::string username(signin_manager_->GetAuthenticatedUsername()); + if (!username.length()) { + // User is not signed in. + CleanAfterFailure(); + Notify(); + return; + } + // Find a token. + OAuth2TokenService::ScopeSet scopes; + scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); + token_request_ = token_service_->StartRequest(username, scopes, this); +} + +// +// OAuth2AccessTokenConsumer methods. +// +void BookmarkServerService::OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) { + url_fetcher_.reset(CreateFetcher()); + // Free the token request. + token_request_.reset(); + + if (!url_fetcher_) { + CleanAfterFailure(); + Notify(); + return; + } + url_fetcher_->SetRequestContext(request_context_getter_.get()); + + // Add the token. + std::string headers; + headers = "Authorization: Bearer "; + headers += access_token; + headers += "\r\n"; + url_fetcher_->SetExtraRequestHeaders(headers); + + // Do not pollute the cookie store with cruft, or mix the users cookie in this + // request. + url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | + net::LOAD_DO_NOT_SAVE_COOKIES); + + url_fetcher_->Start(); +} + +void BookmarkServerService::OnGetTokenFailure( + const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) { + // Free the request. + token_request_.reset(); + CleanAfterFailure(); + Notify(); +} + +// +// net::URLFetcherDelegate methods. +// +void BookmarkServerService::OnURLFetchComplete(const net::URLFetcher* source) { + scoped_ptr<net::URLFetcher> url_fetcher(url_fetcher_.Pass()); + std::string response; + bool should_notify = true; + + if (url_fetcher->GetResponseCode() != 200 || + !url_fetcher->GetResponseAsString(&response) || + !ProcessResponse(response, &should_notify)) { + CleanAfterFailure(); + } + if (should_notify) + Notify(); +} + +// +// BookmarkModelObserver methods. +// +void BookmarkServerService::BookmarkModelLoaded(BookmarkModel* model, + bool ids_reassigned) { + BuildIdMap(); +} + +void BookmarkServerService::BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) { + DCHECK(!inhibit_change_notifications_); + const BookmarkNode* bookmark = parent->GetChild(index); + if (!bookmark->is_url()) + return; + + base::AutoReset<bool> inhibitor(&inhibit_change_notifications_, true); + std::string starid = + enhanced_bookmarks::RemoteIdFromBookmark(model, bookmark); + starsid_to_bookmark_[starid] = bookmark; +} + +void BookmarkServerService::BookmarkNodeRemoved( + BookmarkModel* model, + const BookmarkNode* parent, + int old_index, + const BookmarkNode* node, + const std::set<GURL>& removed_urls) { + DCHECK(!inhibit_change_notifications_); + if (!node->is_url()) + return; + base::AutoReset<bool> inhibitor(&inhibit_change_notifications_, true); + std::string starid = enhanced_bookmarks::RemoteIdFromBookmark(model, node); + starsid_to_bookmark_.erase(starid); +} + +void BookmarkServerService::OnWillChangeBookmarkMetaInfo( + BookmarkModel* model, + const BookmarkNode* node) { + if (!node->is_url() || inhibit_change_notifications_) + return; + base::AutoReset<bool> inhibitor(&inhibit_change_notifications_, true); + std::string starid = enhanced_bookmarks::RemoteIdFromBookmark(model, node); + starsid_to_bookmark_.erase(starid); +} + +void BookmarkServerService::BookmarkMetaInfoChanged(BookmarkModel* model, + const BookmarkNode* node) { + if (!node->is_url() || inhibit_change_notifications_) + return; + + std::string starid = enhanced_bookmarks::RemoteIdFromBookmark(model, node); + starsid_to_bookmark_[starid] = node; +} + +void BookmarkServerService::BookmarkAllUserNodesRemoved( + BookmarkModel* model, + const std::set<GURL>& removed_urls) { + DCHECK(!inhibit_change_notifications_); + starsid_to_bookmark_.clear(); +} + +SigninManagerBase* BookmarkServerService::GetSigninManager() { + DCHECK(signin_manager_); + return signin_manager_; +} + +} // namespace enhanced_bookmarks diff --git a/components/enhanced_bookmarks/bookmark_server_service.h b/components/enhanced_bookmarks/bookmark_server_service.h new file mode 100644 index 0000000..95a4ad4 --- /dev/null +++ b/components/enhanced_bookmarks/bookmark_server_service.h @@ -0,0 +1,156 @@ +// Copyright 2014 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. + +#ifndef COMPONENTS_ENHANCED_BOOKMARKS_BOOKMARK_SERVER_SERVICE_H_ +#define COMPONENTS_ENHANCED_BOOKMARKS_BOOKMARK_SERVER_SERVICE_H_ + +#include <string> +#include <vector> + +#include "components/bookmarks/browser/bookmark_model_observer.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "google_apis/gaia/oauth2_token_service.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_context_getter.h" + +class BookmarkModel; +class ProfileOAuth2TokenService; +class SigninManagerBase; + +namespace enhanced_bookmarks { + +class BookmarkServerService; + +class BookmarkServerServiceObserver { + public: + virtual void OnChange(BookmarkServerService* service) = 0; + + protected: + virtual ~BookmarkServerServiceObserver() {} +}; + +// This abstract class manages the connection to the bookmark servers and +// stores the maps necessary to translate the response from stars.id to +// BookmarkNodes. Subclasses just have to provide the right query and the +// parsing of the response. +class BookmarkServerService : protected net::URLFetcherDelegate, + protected BookmarkModelObserver, + private OAuth2TokenService::Consumer { + public: + BookmarkServerService( + scoped_refptr<net::URLRequestContextGetter> request_context_getter, + ProfileOAuth2TokenService* token_service, + SigninManagerBase* signin_manager, + BookmarkModel* bookmark_model); + virtual ~BookmarkServerService(); + + void AddObserver(BookmarkServerServiceObserver* observer); + void RemoveObserver(BookmarkServerServiceObserver* observer); + + protected: + // Retrieves a bookmark by using its remote id. Returns null if nothing + // matches. + virtual const BookmarkNode* BookmarkForRemoteId( + const std::string& remote_id) const; + const std::string RemoteIDForBookmark(const BookmarkNode* bookmark) const; + + // Notifies the observers that something changed. + void Notify(); + + // Triggers a fetch. + void TriggerTokenRequest(bool cancel_previous); + + // Build the query to send to the server. Returns a newly created url_fetcher. + virtual net::URLFetcher* CreateFetcher() = 0; + + // Processes the response to the query. Returns true on successful parsing, + // false on failure. The implementation can assume that |should_notify| is set + // to true by default, if changed to false there will be no OnChange + // notification send. + virtual bool ProcessResponse(const std::string& response, + bool* should_notify) = 0; + + // If the token can't be retrieved or the query fails this method is called. + virtual void CleanAfterFailure() = 0; + + // BookmarkModelObserver methods. + virtual void BookmarkModelLoaded(BookmarkModel* model, + bool ids_reassigned) OVERRIDE; + virtual void BookmarkNodeMoved(BookmarkModel* model, + const BookmarkNode* old_parent, + int old_index, + const BookmarkNode* new_parent, + int new_index) OVERRIDE {}; + virtual void BookmarkNodeAdded(BookmarkModel* model, + const BookmarkNode* parent, + int index) OVERRIDE; + virtual void BookmarkNodeRemoved(BookmarkModel* model, + const BookmarkNode* parent, + int old_index, + const BookmarkNode* node, + const std::set<GURL>& removed_urls) OVERRIDE; + virtual void BookmarkNodeChanged(BookmarkModel* model, + const BookmarkNode* node) OVERRIDE {}; + virtual void OnWillChangeBookmarkMetaInfo(BookmarkModel* model, + const BookmarkNode* node) OVERRIDE; + + virtual void BookmarkMetaInfoChanged(BookmarkModel* model, + const BookmarkNode* node) OVERRIDE; + + virtual void BookmarkNodeFaviconChanged(BookmarkModel* model, + const BookmarkNode* node) OVERRIDE {}; + virtual void BookmarkNodeChildrenReordered(BookmarkModel* model, + const BookmarkNode* node) + OVERRIDE {}; + virtual void BookmarkAllUserNodesRemoved( + BookmarkModel* model, + const std::set<GURL>& removed_urls) OVERRIDE; + + SigninManagerBase* GetSigninManager(); + + // Cached pointer to the bookmarks model. + BookmarkModel* bookmark_model_; // weak + + private: + FRIEND_TEST_ALL_PREFIXES(BookmarkServerServiceTest, Cluster); + FRIEND_TEST_ALL_PREFIXES(BookmarkServerServiceTest, + ClearClusterMapOnRemoveAllBookmarks); + + // Once the model is ready this method fills in the starsid_to_bookmark_ map. + void BuildIdMap(); + + // net::URLFetcherDelegate methods. Called when the query is finished. + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; + + // OAuth2TokenService::Consumer methods. + virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) OVERRIDE; + virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) OVERRIDE; + + // The observers. + ObserverList<BookmarkServerServiceObserver> observers_; + // The Auth service is used to get a token for auth with the server. + ProfileOAuth2TokenService* token_service_; // Weak + // The request to the token service. + scoped_ptr<OAuth2TokenService::Request> token_request_; + // To get the currently signed in user. + SigninManagerBase* signin_manager_; // Weak + // To have access to the right context getter for the profile. + scoped_refptr<net::URLRequestContextGetter> request_context_getter_; + // The fetcher used to query the server. + scoped_ptr<net::URLFetcher> url_fetcher_; + // A map from stars.id to bookmark nodes. With no null entries. + std::map<std::string, const BookmarkNode*> starsid_to_bookmark_; + // Set to true during the creation of a new bookmark in order to send only the + // proper notification. + bool inhibit_change_notifications_; + + DISALLOW_COPY_AND_ASSIGN(BookmarkServerService); +}; +} // namespace enhanced_bookmarks + +#endif // COMPONENTS_ENHANCED_BOOKMARKS_BOOKMARK_SERVER_SERVICE_H_ diff --git a/components/enhanced_bookmarks/proto/BUILD.gn b/components/enhanced_bookmarks/proto/BUILD.gn index ddea52b..f6e932d 100644 --- a/components/enhanced_bookmarks/proto/BUILD.gn +++ b/components/enhanced_bookmarks/proto/BUILD.gn @@ -7,5 +7,6 @@ import("//third_party/protobuf/proto_library.gni") proto_library("proto") { sources = [ "metadata.proto", + "search.proto", ] } diff --git a/components/enhanced_bookmarks/proto/search.proto b/components/enhanced_bookmarks/proto/search.proto new file mode 100644 index 0000000..df508ab --- /dev/null +++ b/components/enhanced_bookmarks/proto/search.proto @@ -0,0 +1,30 @@ +// Copyright 2014 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. +// +syntax = "proto2"; +option optimize_for = LITE_RUNTIME; + +package image.collections; + +// Contains the result of a full text search. +message CorpusSearchResult { + // Encodes the status of the response. + enum Status { + UNKNOWN = 0; + OK = 1; + FAILED_RPC = 2; + NO_VALID_BACKEND = 3; + INVALID_INPUT = 4; + } + optional Status status = 1; + + // For each results returns the clip id, the title and a snippet highlighting + // the context of the match on the search term. + message ClipResult { + optional string clip_id = 1; + optional string title = 2; + optional string snippet = 3; + } + repeated ClipResult results = 2; +} |