Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(549)

Side by Side Diff: chrome/browser/search_engines/template_url_service.h

Issue 7566036: Implement SyncableServices in TemplateURLService. Add related unittests. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Initial upload Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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 #ifndef CHROME_BROWSER_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_ 5 #ifndef CHROME_BROWSER_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_
6 #define CHROME_BROWSER_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_ 6 #define CHROME_BROWSER_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_
7 #pragma once 7 #pragma once
8 8
9 #include <map> 9 #include <map>
10 #include <set> 10 #include <set>
11 #include <string> 11 #include <string>
12 #include <vector> 12 #include <vector>
13 13
14 #include "base/gtest_prod_util.h" 14 #include "base/gtest_prod_util.h"
15 #include "base/memory/scoped_ptr.h" 15 #include "base/memory/scoped_ptr.h"
16 #include "base/observer_list.h" 16 #include "base/observer_list.h"
17 #include "chrome/browser/profiles/profile_keyed_service.h" 17 #include "chrome/browser/profiles/profile_keyed_service.h"
18 #include "chrome/browser/search_engines/search_host_to_urls_map.h" 18 #include "chrome/browser/search_engines/search_host_to_urls_map.h"
19 #include "chrome/browser/search_engines/template_url_id.h" 19 #include "chrome/browser/search_engines/template_url_id.h"
20 #include "chrome/browser/sync/api/sync_change.h"
21 #include "chrome/browser/sync/api/syncable_service.h"
22 #include "chrome/browser/sync/protocol/search_engine_specifics.pb.h"
20 #include "chrome/browser/webdata/web_data_service.h" 23 #include "chrome/browser/webdata/web_data_service.h"
21 #include "content/common/notification_observer.h" 24 #include "content/common/notification_observer.h"
22 #include "content/common/notification_registrar.h" 25 #include "content/common/notification_registrar.h"
23 26
24 class GURL; 27 class GURL;
25 class Extension; 28 class Extension;
26 class PrefService; 29 class PrefService;
27 class Profile; 30 class Profile;
28 class PrefSetObserver; 31 class PrefSetObserver;
29 class SearchHostToURLsMap; 32 class SearchHostToURLsMap;
30 class SearchTermsData; 33 class SearchTermsData;
34 class SyncData;
31 class TemplateURLServiceObserver; 35 class TemplateURLServiceObserver;
32 class TemplateURLRef; 36 class TemplateURLRef;
33 37
34 namespace history { 38 namespace history {
35 struct URLVisitedDetails; 39 struct URLVisitedDetails;
36 } 40 }
37 41
42 typedef std::map<std::string, SyncData> SyncDataMap;
Peter Kasting 2011/08/09 21:31:04 Nit: Should this typedef be inside the class? See
SteveT 2011/08/10 15:15:12 Done.
43
38 // TemplateURLService is the backend for keywords. It's used by 44 // TemplateURLService is the backend for keywords. It's used by
39 // KeywordAutocomplete. 45 // KeywordAutocomplete.
40 // 46 //
41 // TemplateURLService stores a vector of TemplateURLs. The TemplateURLs are 47 // TemplateURLService stores a vector of TemplateURLs. The TemplateURLs are
42 // persisted to the database maintained by WebDataService. *ALL* mutations 48 // persisted to the database maintained by WebDataService. *ALL* mutations
43 // to the TemplateURLs must funnel through TemplateURLService. This allows 49 // to the TemplateURLs must funnel through TemplateURLService. This allows
44 // TemplateURLService to notify listeners of changes as well as keep the 50 // TemplateURLService to notify listeners of changes as well as keep the
45 // database in sync. 51 // database in sync.
46 // 52 //
47 // There is a TemplateURLService per Profile. 53 // There is a TemplateURLService per Profile.
48 // 54 //
49 // TemplateURLService does not load the vector of TemplateURLs in its 55 // TemplateURLService does not load the vector of TemplateURLs in its
50 // constructor (except for testing). Use the Load method to trigger a load. 56 // constructor (except for testing). Use the Load method to trigger a load.
51 // When TemplateURLService has completed loading, observers are notified via 57 // When TemplateURLService has completed loading, observers are notified via
52 // OnTemplateURLServiceChanged as well as the TEMPLATE_URL_SERVICE_LOADED 58 // OnTemplateURLServiceChanged as well as the TEMPLATE_URL_SERVICE_LOADED
53 // notification message. 59 // notification message.
54 // 60 //
55 // TemplateURLService takes ownership of any TemplateURL passed to it. If there 61 // TemplateURLService takes ownership of any TemplateURL passed to it. If there
56 // is a WebDataService, deletion is handled by WebDataService, otherwise 62 // is a WebDataService, deletion is handled by WebDataService, otherwise
57 // TemplateURLService handles deletion. 63 // TemplateURLService handles deletion.
58 64
59 class TemplateURLService : public WebDataServiceConsumer, 65 class TemplateURLService : public WebDataServiceConsumer,
60 public ProfileKeyedService, 66 public ProfileKeyedService,
61 public NotificationObserver { 67 public NotificationObserver,
68 public SyncableService {
62 public: 69 public:
63 typedef std::map<std::string, std::string> QueryTerms; 70 typedef std::map<std::string, std::string> QueryTerms;
64 typedef std::vector<const TemplateURL*> TemplateURLVector; 71 typedef std::vector<const TemplateURL*> TemplateURLVector;
65 // Type for a static function pointer that acts as a time source. 72 // Type for a static function pointer that acts as a time source.
66 typedef base::Time(TimeProvider)(); 73 typedef base::Time(TimeProvider)();
67 74
68 // Struct used for initializing the data store with fake data. 75 // Struct used for initializing the data store with fake data.
69 // Each initializer is mapped to a TemplateURL. 76 // Each initializer is mapped to a TemplateURL.
70 struct Initializer { 77 struct Initializer {
71 const char* const keyword; 78 const char* const keyword;
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
117 void FindMatchingKeywords(const string16& prefix, 124 void FindMatchingKeywords(const string16& prefix,
118 bool support_replacement_only, 125 bool support_replacement_only,
119 std::vector<string16>* matches) const; 126 std::vector<string16>* matches) const;
120 127
121 // Looks up |keyword| and returns the element it maps to. Returns NULL if 128 // Looks up |keyword| and returns the element it maps to. Returns NULL if
122 // the keyword was not found. 129 // the keyword was not found.
123 // The caller should not try to delete the returned pointer; the data store 130 // The caller should not try to delete the returned pointer; the data store
124 // retains ownership of it. 131 // retains ownership of it.
125 const TemplateURL* GetTemplateURLForKeyword(const string16& keyword) const; 132 const TemplateURL* GetTemplateURLForKeyword(const string16& keyword) const;
126 133
134 // Looks up |sync_guid| and returns the element it maps to. Returns NULL if
135 // the guid was not found.
136 // The caller should not try to delete the returned pointer; the data store
137 // retains ownership of it.
138 const TemplateURL* GetTemplateURLForGUID(const std::string& sync_guid) const;
139
127 // Returns the first TemplateURL found with a URL using the specified |host|, 140 // Returns the first TemplateURL found with a URL using the specified |host|,
128 // or NULL if there are no such TemplateURLs 141 // or NULL if there are no such TemplateURLs
129 const TemplateURL* GetTemplateURLForHost(const std::string& host) const; 142 const TemplateURL* GetTemplateURLForHost(const std::string& host) const;
130 143
131 // Adds a new TemplateURL to this model. TemplateURLService will own the 144 // Adds a new TemplateURL to this model. TemplateURLService will own the
132 // reference, and delete it when the TemplateURL is removed. 145 // reference, and delete it when the TemplateURL is removed.
133 void Add(TemplateURL* template_url); 146 void Add(TemplateURL* template_url);
134 147
135 // Removes the keyword from the model. This deletes the supplied TemplateURL. 148 // Removes the keyword from the model. This deletes the supplied TemplateURL.
136 // This fails if the supplied template_url is the default search provider. 149 // This fails if the supplied template_url is the default search provider.
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
168 // Called when a URL is loaded that was generated from a keyword. 181 // Called when a URL is loaded that was generated from a keyword.
169 void IncrementUsageCount(const TemplateURL* url); 182 void IncrementUsageCount(const TemplateURL* url);
170 183
171 // Resets the title, keyword and search url of the specified TemplateURL. 184 // Resets the title, keyword and search url of the specified TemplateURL.
172 // The TemplateURL is marked as not replaceable. 185 // The TemplateURL is marked as not replaceable.
173 void ResetTemplateURL(const TemplateURL* url, 186 void ResetTemplateURL(const TemplateURL* url,
174 const string16& title, 187 const string16& title,
175 const string16& keyword, 188 const string16& keyword,
176 const std::string& search_url); 189 const std::string& search_url);
177 190
191 // Resets the title, keyword, search url and sync GUID of the specified
192 // TemplateURL. The TemplateURL is marked as not replaceable.
193 void ResetTemplateURL(const TemplateURL* url,
Peter Kasting 2011/08/09 21:31:04 By adding this, we've effectively created a "defau
SteveT 2011/08/10 15:15:12 You're right - the correct thing to do here is to
194 const string16& title,
195 const string16& keyword,
196 const std::string& search_url,
197 const std::string& guid);
198
178 // Return true if the given |url| can be made the default. 199 // Return true if the given |url| can be made the default.
179 bool CanMakeDefault(const TemplateURL* url); 200 bool CanMakeDefault(const TemplateURL* url);
180 201
181 // Set the default search provider. |url| may be null. 202 // Set the default search provider. |url| may be null.
182 // This will assert if the default search is managed; the UI should not be 203 // This will assert if the default search is managed; the UI should not be
183 // invoking this method in that situation. 204 // invoking this method in that situation.
184 void SetDefaultSearchProvider(const TemplateURL* url); 205 void SetDefaultSearchProvider(const TemplateURL* url);
185 206
186 // Returns the default search provider. If the TemplateURLService hasn't been 207 // Returns the default search provider. If the TemplateURLService hasn't been
187 // loaded, the default search provider is pulled from preferences. 208 // loaded, the default search provider is pulled from preferences.
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
226 // notification types: 247 // notification types:
227 // . NOTIFY_HISTORY_URL_VISITED: adds keyword search terms if the visit 248 // . NOTIFY_HISTORY_URL_VISITED: adds keyword search terms if the visit
228 // corresponds to a keyword. 249 // corresponds to a keyword.
229 // . NOTIFY_GOOGLE_URL_UPDATED: updates mapping for any keywords containing 250 // . NOTIFY_GOOGLE_URL_UPDATED: updates mapping for any keywords containing
230 // a google base url replacement term. 251 // a google base url replacement term.
231 // . PREF_CHANGED: checks whether the default search engine has changed. 252 // . PREF_CHANGED: checks whether the default search engine has changed.
232 virtual void Observe(int type, 253 virtual void Observe(int type,
233 const NotificationSource& source, 254 const NotificationSource& source,
234 const NotificationDetails& details); 255 const NotificationDetails& details);
235 256
257 // SyncableService implementation.
258
259 // Returns all syncable TemplateURLs from this model as SyncData. This should
260 // include every search engine and no Extension keywords.
261 virtual SyncDataList GetAllSyncData(syncable::ModelType type) const OVERRIDE;
262 // Process new search engine changes from Sync, merging them into our local
263 // data. This may send notifications if local search engines are added,
264 // updated or removed.
265 virtual SyncError ProcessSyncChanges(
266 const tracked_objects::Location& from_here,
267 const SyncChangeList& change_list) OVERRIDE;
268 // Merge initial search engine data from Sync and push any local changes up
269 // to Sync. This may send notifications if local search engines are added,
270 // updated or removed.
271 virtual SyncError MergeDataAndStartSyncing(
272 syncable::ModelType type,
273 const SyncDataList& initial_sync_data,
274 SyncChangeProcessor* sync_processor) OVERRIDE;
275 virtual void StopSyncing(syncable::ModelType type) OVERRIDE;
276
277 // Processes a local TemplateURL change for Sync. |turl| is the TemplateURL
278 // that has been modified, and |type| is the Sync ChangeType that took place.
279 // This may send a new SyncChange to the cloud. If our model has not yet been
280 // associated with Sync, or if this is triggered by a Sync change, then this
281 // does nothing.
282 void ProcessTemplateURLChange(const TemplateURL* turl,
283 SyncChange::SyncChangeType type);
284
236 Profile* profile() const { return profile_; } 285 Profile* profile() const { return profile_; }
237 286
238 void SetSearchEngineDialogSlot(int slot) { 287 void SetSearchEngineDialogSlot(int slot) {
239 search_engine_dialog_chosen_slot_ = slot; 288 search_engine_dialog_chosen_slot_ = slot;
240 } 289 }
241 290
242 int GetSearchEngineDialogSlot() const { 291 int GetSearchEngineDialogSlot() const {
243 return search_engine_dialog_chosen_slot_; 292 return search_engine_dialog_chosen_slot_;
244 } 293 }
245 294
246 // Registers the preferences used to save a TemplateURL to prefs. 295 // Registers the preferences used to save a TemplateURL to prefs.
247 static void RegisterUserPrefs(PrefService* prefs); 296 static void RegisterUserPrefs(PrefService* prefs);
248 297
298 // Fills |sync_data| with a sync representation of the search engine data from
299 // |turl|. Returns true if successful, false otherwise.
Peter Kasting 2011/08/09 21:31:04 Nit: You can probably just say "Returns whether cr
SteveT 2011/08/10 15:15:12 Changed the wording. Actually this currently alway
Peter Kasting 2011/08/10 17:39:11 Yeah, I'd do that; it's easy to change the signatu
300 static bool CreateSearchEngineSyncData(const TemplateURL& turl,
301 SyncData* sync_data);
302
303 // Read a TemplateURL object from SyncData containing SearchEngineSpecifics.
304 // This does the opposite of CreateSearchEngineSyncData. The caller owns the
305 // returned TemplateURL*.
306 static TemplateURL* ReadSearchEngineSyncData(const SyncData& sync_data);
Peter Kasting 2011/08/09 21:31:04 Nit: These two functions are paired, but one retur
SteveT 2011/08/10 15:15:12 Actually, since our version of CreateSyncData (abo
307
308 // Attempts to generate a unique keyword for |turl| based on its original
309 // keyword. If its keyword is already unique, that is returned. Otherwise, it
310 // tries to return the autogenerated keyword if that is unique to the Service,
311 // and finally it repeatedly appends special characters to the keyword until
312 // it is unique to the Service.
313 string16 UniquifyKeyword(const TemplateURL& turl) const;
314
315 // Given a TemplateURL from Sync, resolves any keyword conflicts by checking
316 // the local keywords and uniquifying either the cloud keyword or a
317 // conflicting local keyword (whichever is older). If the cloud TURL is
318 // changed, then an appropriate SyncChange is appended to |change_list|. If
319 // a local TURL is changed, the service is updated with the new keyword. If
320 // there was no conflict to begin with, this does nothing. In the case of tied
321 // last_modified dates, |sync_turl| wins. Returns true iff there was a
322 // conflict.
323 bool ResolveSyncKeywordConflict(TemplateURL* sync_turl,
324 SyncChangeList& change_list);
325
326 // Returns a TemplateURL from the service that has the same keyword and search
327 // URL as |sync_turl|, if it exists.
328 const TemplateURL* FindDuplicateOfSyncTemplateURL(
329 const TemplateURL& sync_turl);
330
331 // Given a TemplateURL from the cloud and a local matching duplicate found by
332 // FindDuplcateOfSyncTemplateURL, merges the two. If |sync_url| is newer, this
333 // replaces |local_url| with |sync_url| using the service's Remove and Add.
334 // If |local_url| is newer, this copies the GUID from |sync_url| over to
335 // |local_url| and adds an update to change_list to notify the server of the
336 // change.
337 void MergeSyncAndLocalURLDuplicates(TemplateURL* sync_url,
338 TemplateURL* local_url,
339 SyncChangeList& change_list);
340
341 // Returns a map mapping Sync GUIDs to pointers to SyncData.
342 static SyncDataMap CreateGUIDToSyncDataMap(const SyncDataList& sync_data);
343
249 #if defined(UNIT_TEST) 344 #if defined(UNIT_TEST)
250 // Set a different time provider function, such as 345 // Set a different time provider function, such as
251 // base::MockTimeProvider::StaticNow, when testing calls to base::Time::Now. 346 // base::MockTimeProvider::StaticNow, when testing calls to base::Time::Now.
252 void set_time_provider(TimeProvider* time_provider) { 347 void set_time_provider(TimeProvider* time_provider) {
253 time_provider_ = time_provider; 348 time_provider_ = time_provider;
254 } 349 }
255 #endif 350 #endif
256 351
257 protected: 352 protected:
258 // Cover method for the method of the same name on the HistoryService. 353 // Cover method for the method of the same name on the HistoryService.
259 // url is the one that was visited with the given search terms. 354 // url is the one that was visited with the given search terms.
260 // 355 //
261 // This exists and is virtual for testing. 356 // This exists and is virtual for testing.
262 virtual void SetKeywordSearchTermsForURL(const TemplateURL* t_url, 357 virtual void SetKeywordSearchTermsForURL(const TemplateURL* t_url,
263 const GURL& url, 358 const GURL& url,
264 const string16& term); 359 const string16& term);
265 360
266 private: 361 private:
267 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, BuildQueryTerms); 362 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, BuildQueryTerms);
268 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, TestManagedDefaultSearch); 363 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, TestManagedDefaultSearch);
269 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, 364 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest,
270 UpdateKeywordSearchTermsForURL); 365 UpdateKeywordSearchTermsForURL);
271 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, 366 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest,
272 DontUpdateKeywordSearchForNonReplaceable); 367 DontUpdateKeywordSearchForNonReplaceable);
273 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, ChangeGoogleBaseValue); 368 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, ChangeGoogleBaseValue);
274 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, MergeDeletesUnusedProviders); 369 FRIEND_TEST_ALL_PREFIXES(TemplateURLServiceTest, MergeDeletesUnusedProviders);
275 friend class TemplateURLServiceTestUtil; 370 friend class TemplateURLServiceTestUtil;
276 371
277 typedef std::map<string16, const TemplateURL*> KeywordToTemplateMap; 372 typedef std::map<string16, const TemplateURL*> KeywordToTemplateMap;
373 typedef std::map<std::string, const TemplateURL*> GUIDToTemplateMap;
278 374
279 // Helper functor for FindMatchingKeywords(), for finding the range of 375 // Helper functor for FindMatchingKeywords(), for finding the range of
280 // keywords which begin with a prefix. 376 // keywords which begin with a prefix.
281 class LessWithPrefix; 377 class LessWithPrefix;
282 378
283 void Init(const Initializer* initializers, int num_initializers); 379 void Init(const Initializer* initializers, int num_initializers);
284 380
285 void RemoveFromMaps(const TemplateURL* template_url); 381 void RemoveFromMaps(const TemplateURL* template_url);
286 382
287 // Removes the supplied template_url from the keyword maps. This searches 383 // Removes the supplied template_url from the keyword maps. This searches
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
393 void RemoveProvidersCreatedByPolicy( 489 void RemoveProvidersCreatedByPolicy(
394 std::vector<TemplateURL*>* template_urls, 490 std::vector<TemplateURL*>* template_urls,
395 const TemplateURL** default_search_provider, 491 const TemplateURL** default_search_provider,
396 const TemplateURL* default_from_prefs); 492 const TemplateURL* default_from_prefs);
397 493
398 NotificationRegistrar registrar_; 494 NotificationRegistrar registrar_;
399 495
400 // Mapping from keyword to the TemplateURL. 496 // Mapping from keyword to the TemplateURL.
401 KeywordToTemplateMap keyword_to_template_map_; 497 KeywordToTemplateMap keyword_to_template_map_;
402 498
499 // Mapping from Sync GUIDs to the TemplateURL.
500 GUIDToTemplateMap guid_to_template_map_;
501
403 TemplateURLVector template_urls_; 502 TemplateURLVector template_urls_;
404 503
405 ObserverList<TemplateURLServiceObserver> model_observers_; 504 ObserverList<TemplateURLServiceObserver> model_observers_;
406 505
407 // Maps from host to set of TemplateURLs whose search url host is host. 506 // Maps from host to set of TemplateURLs whose search url host is host.
408 SearchHostToURLsMap provider_map_; 507 SearchHostToURLsMap provider_map_;
409 508
410 // Used to obtain the WebDataService. 509 // Used to obtain the WebDataService.
411 // When Load is invoked, if we haven't yet loaded, the WebDataService is 510 // When Load is invoked, if we haven't yet loaded, the WebDataService is
412 // obtained from the Profile. This allows us to lazily access the database. 511 // obtained from the Profile. This allows us to lazily access the database.
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
450 // ID assigned to next TemplateURL added to this model. This is an ever 549 // ID assigned to next TemplateURL added to this model. This is an ever
451 // increasing integer that is initialized from the database. 550 // increasing integer that is initialized from the database.
452 TemplateURLID next_id_; 551 TemplateURLID next_id_;
453 552
454 // List of extension IDs waiting for Load to have keywords registered. 553 // List of extension IDs waiting for Load to have keywords registered.
455 std::vector<std::string> pending_extension_ids_; 554 std::vector<std::string> pending_extension_ids_;
456 555
457 // Function returning current time in base::Time units. 556 // Function returning current time in base::Time units.
458 TimeProvider* time_provider_; 557 TimeProvider* time_provider_;
459 558
559 // Do we have an active association between the TemplateURLs and sync models?
560 // Set when start syncing, reset in StopSyncing. While this is not set, we
Peter Kasting 2011/08/09 21:31:04 Nit: "Set when start syncing" isn't grammatical (m
SteveT 2011/08/10 15:15:12 Done.
561 // ignore any local search engine changes (when we start syncing we will look
562 // up the most recent values anyways).
563 bool models_associated_;
564
565 // Whether we're currently processing changes from the syncer. While this is
566 // true, we ignore any local search engine changes, since we triggered them.
567 bool processing_syncer_changes_;
568
569 // Sync's SyncChange handler. We push all our changes through this.
570 SyncChangeProcessor* sync_processor_;
571
460 DISALLOW_COPY_AND_ASSIGN(TemplateURLService); 572 DISALLOW_COPY_AND_ASSIGN(TemplateURLService);
461 }; 573 };
462 574
463 #endif // CHROME_BROWSER_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_ 575 #endif // CHROME_BROWSER_SEARCH_ENGINES_TEMPLATE_URL_SERVICE_H_
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698