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 <errno.h> | 5 #include <errno.h> |
6 #include <string> | 6 #include <string> |
7 #include <vector> | 7 #include <vector> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/bind_helpers.h" | 10 #include "base/bind_helpers.h" |
11 #include "base/file_path.h" | 11 #include "base/file_path.h" |
12 #include "base/file_util.h" | 12 #include "base/file_util.h" |
13 #include "base/json/json_file_value_serializer.h" | 13 #include "base/json/json_file_value_serializer.h" |
14 #include "base/memory/ref_counted.h" | 14 #include "base/memory/ref_counted.h" |
15 #include "base/memory/scoped_ptr.h" | 15 #include "base/memory/scoped_ptr.h" |
16 #include "base/message_loop.h" | 16 #include "base/message_loop.h" |
17 #include "base/path_service.h" | 17 #include "base/path_service.h" |
18 #include "base/string16.h" | 18 #include "base/string16.h" |
19 #include "base/string_util.h" | 19 #include "base/string_util.h" |
20 #include "base/threading/sequenced_worker_pool.h" | 20 #include "base/threading/sequenced_worker_pool.h" |
21 #include "base/time.h" | 21 #include "base/time.h" |
22 #include "base/utf_string_conversions.h" | 22 #include "base/utf_string_conversions.h" |
23 #include "base/values.h" | 23 #include "base/values.h" |
24 #include "chrome/browser/chromeos/gdata/gdata.pb.h" | 24 #include "chrome/browser/chromeos/gdata/gdata.pb.h" |
25 #include "chrome/browser/chromeos/gdata/gdata_file_system.h" | 25 #include "chrome/browser/chromeos/gdata/gdata_file_system.h" |
26 #include "chrome/browser/chromeos/gdata/gdata_parser.h" | 26 #include "chrome/browser/chromeos/gdata/gdata_parser.h" |
| 27 #include "chrome/browser/chromeos/gdata/gdata_util.h" |
27 #include "chrome/browser/chromeos/gdata/mock_directory_change_observer.h" | 28 #include "chrome/browser/chromeos/gdata/mock_directory_change_observer.h" |
28 #include "chrome/browser/chromeos/gdata/mock_gdata_documents_service.h" | 29 #include "chrome/browser/chromeos/gdata/mock_gdata_documents_service.h" |
29 #include "chrome/browser/chromeos/gdata/mock_gdata_sync_client.h" | 30 #include "chrome/browser/chromeos/gdata/mock_gdata_sync_client.h" |
30 #include "chrome/common/chrome_paths.h" | 31 #include "chrome/common/chrome_paths.h" |
31 #include "chrome/test/base/testing_profile.h" | 32 #include "chrome/test/base/testing_profile.h" |
32 #include "content/test/test_browser_thread.h" | 33 #include "content/test/test_browser_thread.h" |
33 #include "content/public/browser/browser_thread.h" | 34 #include "content/public/browser/browser_thread.h" |
34 #include "testing/gmock/include/gmock/gmock.h" | 35 #include "testing/gmock/include/gmock/gmock.h" |
35 #include "testing/gtest/include/gtest/gtest.h" | 36 #include "testing/gtest/include/gtest/gtest.h" |
36 | 37 |
37 using ::testing::AnyNumber; | 38 using ::testing::AnyNumber; |
38 using ::testing::AtLeast; | 39 using ::testing::AtLeast; |
39 using ::testing::Eq; | 40 using ::testing::Eq; |
40 using ::testing::IsNull; | 41 using ::testing::IsNull; |
41 using ::testing::Ne; | 42 using ::testing::Ne; |
42 using ::testing::NotNull; | 43 using ::testing::NotNull; |
43 using ::testing::Return; | 44 using ::testing::Return; |
44 using ::testing::ReturnNull; | 45 using ::testing::ReturnNull; |
45 using ::testing::_; | 46 using ::testing::_; |
46 | 47 |
47 using base::Value; | 48 using base::Value; |
48 using base::DictionaryValue; | 49 using base::DictionaryValue; |
49 using base::ListValue; | 50 using base::ListValue; |
50 using content::BrowserThread; | 51 using content::BrowserThread; |
51 | 52 |
52 namespace { | 53 namespace { |
53 | 54 |
54 const char kSlash[] = "/"; | |
55 const char kEscapedSlash[] = "\xE2\x88\x95"; | |
56 const char kSymLinkToDevNull[] = "/dev/null"; | 55 const char kSymLinkToDevNull[] = "/dev/null"; |
57 | 56 |
58 struct InitialCacheResource { | 57 struct InitialCacheResource { |
59 const char* source_file; // Source file to be used for cache. | 58 const char* source_file; // Source file to be used for cache. |
60 const char* resource_id; // Resource id of cache file. | 59 const char* resource_id; // Resource id of cache file. |
61 const char* md5; // MD5 of cache file. | 60 const char* md5; // MD5 of cache file. |
62 int cache_state; // Cache state of cache file. | 61 int cache_state; // Cache state of cache file. |
63 const char* expected_file_extension; // Expected extension of cached file. | 62 const char* expected_file_extension; // Expected extension of cached file. |
64 // Expected CacheSubDirectoryType of cached file. | 63 // Expected CacheSubDirectoryType of cached file. |
65 gdata::GDataRootDirectory::CacheSubDirectoryType expected_sub_dir_type; | 64 gdata::GDataRootDirectory::CacheSubDirectoryType expected_sub_dir_type; |
(...skipping 249 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
315 GDataRootDirectory::CACHE_TYPE_TMP, | 314 GDataRootDirectory::CACHE_TYPE_TMP, |
316 GDataFileSystem::CACHED_FILE_FROM_SERVER); | 315 GDataFileSystem::CACHED_FILE_FROM_SERVER); |
317 FilePath expected_path = | 316 FilePath expected_path = |
318 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_TMP]; | 317 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_TMP]; |
319 expected_path = expected_path.Append(expected_filename); | 318 expected_path = expected_path.Append(expected_filename); |
320 EXPECT_EQ(expected_path, actual_path); | 319 EXPECT_EQ(expected_path, actual_path); |
321 | 320 |
322 FilePath base_name = actual_path.BaseName(); | 321 FilePath base_name = actual_path.BaseName(); |
323 | 322 |
324 // FilePath::Extension returns ".", so strip it. | 323 // FilePath::Extension returns ".", so strip it. |
325 std::string unescaped_md5 = GDataEntry::UnescapeUtf8FileName( | 324 std::string unescaped_md5 = util::UnescapeCacheFileName( |
326 base_name.Extension().substr(1)); | 325 base_name.Extension().substr(1)); |
327 EXPECT_EQ(md5, unescaped_md5); | 326 EXPECT_EQ(md5, unescaped_md5); |
328 std::string unescaped_resource_id = GDataEntry::UnescapeUtf8FileName( | 327 std::string unescaped_resource_id = util::UnescapeCacheFileName( |
329 base_name.RemoveExtension().value()); | 328 base_name.RemoveExtension().value()); |
330 EXPECT_EQ(resource_id, unescaped_resource_id); | 329 EXPECT_EQ(resource_id, unescaped_resource_id); |
331 } | 330 } |
332 | 331 |
333 void TestStoreToCache( | 332 void TestStoreToCache( |
334 const std::string& resource_id, | 333 const std::string& resource_id, |
335 const std::string& md5, | 334 const std::string& md5, |
336 const FilePath& source_path, | 335 const FilePath& source_path, |
337 base::PlatformFileError expected_error, | 336 base::PlatformFileError expected_error, |
338 int expected_cache_state, | 337 int expected_cache_state, |
(...skipping 30 matching lines...) Expand all Loading... |
369 const std::string& md5, | 368 const std::string& md5, |
370 const FilePath& gdata_file_path, | 369 const FilePath& gdata_file_path, |
371 const FilePath& cache_file_path) { | 370 const FilePath& cache_file_path) { |
372 ++num_callback_invocations_; | 371 ++num_callback_invocations_; |
373 | 372 |
374 EXPECT_EQ(expected_error_, error); | 373 EXPECT_EQ(expected_error_, error); |
375 | 374 |
376 if (error == base::PLATFORM_FILE_OK) { | 375 if (error == base::PLATFORM_FILE_OK) { |
377 // Verify filename of |cache_file_path|. | 376 // Verify filename of |cache_file_path|. |
378 FilePath base_name = cache_file_path.BaseName(); | 377 FilePath base_name = cache_file_path.BaseName(); |
379 EXPECT_EQ(GDataEntry::EscapeUtf8FileName(resource_id) + | 378 EXPECT_EQ(util::EscapeCacheFileName(resource_id) + |
380 FilePath::kExtensionSeparator + | 379 FilePath::kExtensionSeparator + |
381 GDataEntry::EscapeUtf8FileName( | 380 util::EscapeCacheFileName( |
382 expected_file_extension_.empty() ? | 381 expected_file_extension_.empty() ? |
383 md5 : expected_file_extension_), | 382 md5 : expected_file_extension_), |
384 base_name.value()); | 383 base_name.value()); |
385 } else { | 384 } else { |
386 EXPECT_TRUE(cache_file_path.empty()); | 385 EXPECT_TRUE(cache_file_path.empty()); |
387 } | 386 } |
388 } | 387 } |
389 | 388 |
390 void TestRemoveFromCache(const std::string& resource_id, | 389 void TestRemoveFromCache(const std::string& resource_id, |
391 base::PlatformFileError expected_error) { | 390 base::PlatformFileError expected_error) { |
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
575 void VerifyMarkDirty(base::PlatformFileError error, | 574 void VerifyMarkDirty(base::PlatformFileError error, |
576 const std::string& resource_id, | 575 const std::string& resource_id, |
577 const std::string& md5, | 576 const std::string& md5, |
578 const FilePath& gdata_file_path, | 577 const FilePath& gdata_file_path, |
579 const FilePath& cache_file_path) { | 578 const FilePath& cache_file_path) { |
580 VerifyCacheFileState(error, resource_id, md5); | 579 VerifyCacheFileState(error, resource_id, md5); |
581 | 580 |
582 // Verify filename of |cache_file_path|. | 581 // Verify filename of |cache_file_path|. |
583 if (error == base::PLATFORM_FILE_OK) { | 582 if (error == base::PLATFORM_FILE_OK) { |
584 FilePath base_name = cache_file_path.BaseName(); | 583 FilePath base_name = cache_file_path.BaseName(); |
585 EXPECT_EQ(GDataEntry::EscapeUtf8FileName(resource_id) + | 584 EXPECT_EQ(util::EscapeCacheFileName(resource_id) + |
586 FilePath::kExtensionSeparator + | 585 FilePath::kExtensionSeparator + |
587 "local", | 586 "local", |
588 base_name.value()); | 587 base_name.value()); |
589 } else { | 588 } else { |
590 EXPECT_TRUE(cache_file_path.empty()); | 589 EXPECT_TRUE(cache_file_path.empty()); |
591 } | 590 } |
592 } | 591 } |
593 | 592 |
594 void TestCommitDirty( | 593 void TestCommitDirty( |
595 const std::string& resource_id, | 594 const std::string& resource_id, |
(...skipping 24 matching lines...) Expand all Loading... |
620 expected_sub_dir_type_ = expected_sub_dir_type; | 619 expected_sub_dir_type_ = expected_sub_dir_type; |
621 expect_outgoing_symlink_ = false; | 620 expect_outgoing_symlink_ = false; |
622 | 621 |
623 file_system_->ClearDirtyInCache(resource_id, md5, | 622 file_system_->ClearDirtyInCache(resource_id, md5, |
624 base::Bind(&GDataFileSystemTest::VerifyCacheFileState, | 623 base::Bind(&GDataFileSystemTest::VerifyCacheFileState, |
625 base::Unretained(this))); | 624 base::Unretained(this))); |
626 | 625 |
627 RunAllPendingForIO(); | 626 RunAllPendingForIO(); |
628 } | 627 } |
629 | 628 |
| 629 void VerifySetMountedState(const std::string& resource_id, |
| 630 const std::string& md5, |
| 631 bool to_mount, |
| 632 base::PlatformFileError error, |
| 633 const FilePath& file_path) { |
| 634 ++num_callback_invocations_; |
| 635 EXPECT_TRUE(file_util::PathExists(file_path)); |
| 636 EXPECT_TRUE(file_path == file_system_->GetCacheFilePath( |
| 637 resource_id, |
| 638 md5, |
| 639 expected_sub_dir_type_, |
| 640 to_mount ? |
| 641 GDataFileSystemInterface::CACHED_FILE_MOUNTED : |
| 642 GDataFileSystemInterface::CACHED_FILE_FROM_SERVER)); |
| 643 } |
| 644 |
| 645 void TestSetMountedState( |
| 646 const std::string& resource_id, |
| 647 const std::string& md5, |
| 648 const FilePath& file_path, |
| 649 bool to_mount, |
| 650 base::PlatformFileError expected_error, |
| 651 int expected_cache_state, |
| 652 GDataRootDirectory::CacheSubDirectoryType expected_sub_dir_type) { |
| 653 expected_error_ = expected_error; |
| 654 expected_cache_state_ = expected_cache_state; |
| 655 expected_sub_dir_type_ = expected_sub_dir_type; |
| 656 expect_outgoing_symlink_ = false; |
| 657 |
| 658 file_system_->SetMountedState(file_path, to_mount, |
| 659 base::Bind(&GDataFileSystemTest::VerifySetMountedState, |
| 660 base::Unretained(this), resource_id, md5, to_mount)); |
| 661 |
| 662 RunAllPendingForIO(); |
| 663 } |
| 664 |
630 void PrepareForInitCacheTest() { | 665 void PrepareForInitCacheTest() { |
631 // Create gdata cache sub directories. | 666 // Create gdata cache sub directories. |
632 ASSERT_TRUE(file_util::CreateDirectory( | 667 ASSERT_TRUE(file_util::CreateDirectory( |
633 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_PERSISTENT])); | 668 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_PERSISTENT])); |
634 ASSERT_TRUE(file_util::CreateDirectory( | 669 ASSERT_TRUE(file_util::CreateDirectory( |
635 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_TMP])); | 670 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_TMP])); |
636 ASSERT_TRUE(file_util::CreateDirectory( | 671 ASSERT_TRUE(file_util::CreateDirectory( |
637 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_PINNED])); | 672 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_PINNED])); |
638 ASSERT_TRUE(file_util::CreateDirectory( | 673 ASSERT_TRUE(file_util::CreateDirectory( |
639 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_OUTGOING])); | 674 file_system_->cache_paths_[GDataRootDirectory::CACHE_TYPE_OUTGOING])); |
(...skipping 1307 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1947 std::string resource_id("pdf:1a2b"); | 1982 std::string resource_id("pdf:1a2b"); |
1948 std::string md5("abcdef0123456789"); | 1983 std::string md5("abcdef0123456789"); |
1949 TestGetCacheFilePath(resource_id, md5, | 1984 TestGetCacheFilePath(resource_id, md5, |
1950 resource_id + FilePath::kExtensionSeparator + md5); | 1985 resource_id + FilePath::kExtensionSeparator + md5); |
1951 EXPECT_EQ(0, num_callback_invocations_); | 1986 EXPECT_EQ(0, num_callback_invocations_); |
1952 | 1987 |
1953 // Use non-alphanumeric characters for resource id, including '.' which is an | 1988 // Use non-alphanumeric characters for resource id, including '.' which is an |
1954 // extension separator, to test that the characters are escaped and unescaped | 1989 // extension separator, to test that the characters are escaped and unescaped |
1955 // correctly, and '.' doesn't mess up the filename format and operations. | 1990 // correctly, and '.' doesn't mess up the filename format and operations. |
1956 resource_id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?"; | 1991 resource_id = "pdf:`~!@#$%^&*()-_=+[{|]}\\;',<.>/?"; |
1957 std::string escaped_resource_id; | 1992 std::string escaped_resource_id = util::EscapeCacheFileName(resource_id); |
1958 ReplaceChars(resource_id, kSlash, std::string(kEscapedSlash), | 1993 std::string escaped_md5 = util::EscapeCacheFileName(md5); |
1959 &escaped_resource_id); | |
1960 std::string escaped_md5; | |
1961 ReplaceChars(md5, kSlash, std::string(kEscapedSlash), &escaped_md5); | |
1962 num_callback_invocations_ = 0; | 1994 num_callback_invocations_ = 0; |
1963 TestGetCacheFilePath(resource_id, md5, | 1995 TestGetCacheFilePath(resource_id, md5, |
1964 escaped_resource_id + FilePath::kExtensionSeparator + | 1996 escaped_resource_id + FilePath::kExtensionSeparator + |
1965 escaped_md5); | 1997 escaped_md5); |
1966 EXPECT_EQ(0, num_callback_invocations_); | 1998 EXPECT_EQ(0, num_callback_invocations_); |
1967 } | 1999 } |
1968 | 2000 |
1969 TEST_F(GDataFileSystemTest, StoreToCacheSimple) { | 2001 TEST_F(GDataFileSystemTest, StoreToCacheSimple) { |
1970 EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); | 2002 EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); |
1971 | 2003 |
(...skipping 30 matching lines...) Expand all Loading... |
2002 GDataRootDirectory::CACHE_TYPE_PERSISTENT : | 2034 GDataRootDirectory::CACHE_TYPE_PERSISTENT : |
2003 GDataRootDirectory::CACHE_TYPE_TMP, | 2035 GDataRootDirectory::CACHE_TYPE_TMP, |
2004 GDataFileSystem::CACHED_FILE_FROM_SERVER); | 2036 GDataFileSystem::CACHED_FILE_FROM_SERVER); |
2005 file_util::FileEnumerator enumerator(path.DirName(), false, | 2037 file_util::FileEnumerator enumerator(path.DirName(), false, |
2006 file_util::FileEnumerator::FILES, | 2038 file_util::FileEnumerator::FILES, |
2007 path.BaseName().value()); | 2039 path.BaseName().value()); |
2008 size_t num_files_found = 0; | 2040 size_t num_files_found = 0; |
2009 for (FilePath current = enumerator.Next(); !current.empty(); | 2041 for (FilePath current = enumerator.Next(); !current.empty(); |
2010 current = enumerator.Next()) { | 2042 current = enumerator.Next()) { |
2011 ++num_files_found; | 2043 ++num_files_found; |
2012 EXPECT_EQ(GDataEntry::EscapeUtf8FileName(resource_id) + | 2044 EXPECT_EQ(util::EscapeCacheFileName(resource_id) + |
2013 FilePath::kExtensionSeparator + | 2045 FilePath::kExtensionSeparator + |
2014 GDataEntry::EscapeUtf8FileName(md5), | 2046 util::EscapeCacheFileName(md5), |
2015 current.BaseName().value()); | 2047 current.BaseName().value()); |
2016 } | 2048 } |
2017 EXPECT_EQ(1U, num_files_found); | 2049 EXPECT_EQ(1U, num_files_found); |
2018 } | 2050 } |
2019 | 2051 |
2020 TEST_F(GDataFileSystemTest, GetFromCacheSimple) { | 2052 TEST_F(GDataFileSystemTest, GetFromCacheSimple) { |
2021 EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); | 2053 EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); |
2022 | 2054 |
2023 std::string resource_id("pdf:1a2b"); | 2055 std::string resource_id("pdf:1a2b"); |
2024 std::string md5("abcdef0123456789"); | 2056 std::string md5("abcdef0123456789"); |
(...skipping 942 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2967 EXPECT_EQ(12345, callback_helper_->quota_bytes_total_); | 2999 EXPECT_EQ(12345, callback_helper_->quota_bytes_total_); |
2968 | 3000 |
2969 // Verify account meta feed is saved to cache. | 3001 // Verify account meta feed is saved to cache. |
2970 RunAllPendingForIO(); | 3002 RunAllPendingForIO(); |
2971 FilePath path = file_system_->cache_paths_[ | 3003 FilePath path = file_system_->cache_paths_[ |
2972 GDataRootDirectory::CACHE_TYPE_META].Append( | 3004 GDataRootDirectory::CACHE_TYPE_META].Append( |
2973 FILE_PATH_LITERAL("account_metadata.json")); | 3005 FILE_PATH_LITERAL("account_metadata.json")); |
2974 EXPECT_TRUE(file_util::PathExists(path)); | 3006 EXPECT_TRUE(file_util::PathExists(path)); |
2975 } | 3007 } |
2976 | 3008 |
| 3009 TEST_F(GDataFileSystemTest, MountUnmount) { |
| 3010 EXPECT_CALL(*mock_sync_client_, OnCacheInitialized()).Times(1); |
| 3011 |
| 3012 FilePath file_path; |
| 3013 std::string resource_id("pdf:1a2b"); |
| 3014 std::string md5("abcdef0123456789"); |
| 3015 |
| 3016 // First store a file to cache in the tmp subdir. |
| 3017 TestStoreToCache(resource_id, md5, GetTestFilePath("root_feed.json"), |
| 3018 base::PLATFORM_FILE_OK, GDataFile::CACHE_STATE_PRESENT, |
| 3019 GDataRootDirectory::CACHE_TYPE_TMP); |
| 3020 |
| 3021 // Mark the file mounted. |
| 3022 num_callback_invocations_ = 0; |
| 3023 file_path = file_system_->GetCacheFilePath( |
| 3024 resource_id, md5, |
| 3025 GDataRootDirectory::CACHE_TYPE_TMP, |
| 3026 GDataFileSystemInterface::CACHED_FILE_FROM_SERVER); |
| 3027 TestSetMountedState(resource_id, md5, file_path, true, |
| 3028 base::PLATFORM_FILE_OK, |
| 3029 GDataFile::CACHE_STATE_PRESENT | |
| 3030 GDataFile::CACHE_STATE_MOUNTED, |
| 3031 GDataRootDirectory::CACHE_TYPE_PERSISTENT); |
| 3032 EXPECT_EQ(1, num_callback_invocations_); |
| 3033 EXPECT_TRUE(CacheEntryExists(resource_id, md5)); |
| 3034 |
| 3035 // Clear mounted state of the file. |
| 3036 num_callback_invocations_ = 0; |
| 3037 file_path = file_system_->GetCacheFilePath( |
| 3038 resource_id, |
| 3039 md5, |
| 3040 GDataRootDirectory::CACHE_TYPE_PERSISTENT, |
| 3041 GDataFileSystemInterface::CACHED_FILE_MOUNTED); |
| 3042 TestSetMountedState(resource_id, md5, file_path, false, |
| 3043 base::PLATFORM_FILE_OK, |
| 3044 GDataFile::CACHE_STATE_PRESENT, |
| 3045 GDataRootDirectory::CACHE_TYPE_TMP); |
| 3046 EXPECT_EQ(1, num_callback_invocations_); |
| 3047 EXPECT_TRUE(CacheEntryExists(resource_id, md5)); |
| 3048 |
| 3049 // Try to remove the file. |
| 3050 num_callback_invocations_ = 0; |
| 3051 TestRemoveFromCache(resource_id, base::PLATFORM_FILE_OK); |
| 3052 EXPECT_EQ(1, num_callback_invocations_); |
| 3053 } |
| 3054 |
2977 } // namespace gdata | 3055 } // namespace gdata |
OLD | NEW |