| 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 "webkit/fileapi/syncable/local_file_sync_context.h" | 5 #include "webkit/fileapi/syncable/local_file_sync_context.h" |
| 6 | 6 |
| 7 #include <vector> | 7 #include <vector> |
| 8 | 8 |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/file_path.h" | 10 #include "base/file_path.h" |
| (...skipping 16 matching lines...) Expand all Loading... |
| 27 #include "webkit/fileapi/syncable/syncable_file_system_util.h" | 27 #include "webkit/fileapi/syncable/syncable_file_system_util.h" |
| 28 | 28 |
| 29 #define FPL FILE_PATH_LITERAL | 29 #define FPL FILE_PATH_LITERAL |
| 30 | 30 |
| 31 // This tests LocalFileSyncContext behavior in multi-thread / | 31 // This tests LocalFileSyncContext behavior in multi-thread / |
| 32 // multi-file-system-context environment. | 32 // multi-file-system-context environment. |
| 33 // Basic combined tests (single-thread / single-file-system-context) | 33 // Basic combined tests (single-thread / single-file-system-context) |
| 34 // that involve LocalFileSyncContext are also in | 34 // that involve LocalFileSyncContext are also in |
| 35 // syncable_file_system_unittests.cc. | 35 // syncable_file_system_unittests.cc. |
| 36 | 36 |
| 37 using sync_file_system::FileChange; |
| 38 using sync_file_system::FileChangeList; |
| 39 using sync_file_system::SyncFileType; |
| 40 |
| 37 namespace fileapi { | 41 namespace fileapi { |
| 38 | 42 |
| 39 namespace { | 43 namespace { |
| 40 const char kOrigin1[] = "http://example.com"; | 44 const char kOrigin1[] = "http://example.com"; |
| 41 const char kOrigin2[] = "http://chromium.org"; | 45 const char kOrigin2[] = "http://chromium.org"; |
| 42 const char kServiceName[] = "test"; | 46 const char kServiceName[] = "test"; |
| 43 } | 47 } |
| 44 | 48 |
| 45 class LocalFileSyncContextTest : public testing::Test { | 49 class LocalFileSyncContextTest : public testing::Test { |
| 46 protected: | 50 protected: |
| (...skipping 253 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 300 ASSERT_EQ(1U, urls.size()); | 304 ASSERT_EQ(1U, urls.size()); |
| 301 EXPECT_TRUE(ContainsKey(urls, kURL2)); | 305 EXPECT_TRUE(ContainsKey(urls, kURL2)); |
| 302 | 306 |
| 303 SyncFileMetadata metadata; | 307 SyncFileMetadata metadata; |
| 304 FileChangeList changes; | 308 FileChangeList changes; |
| 305 EXPECT_EQ(SYNC_STATUS_OK, PrepareForSync(file_system1.file_system_context(), | 309 EXPECT_EQ(SYNC_STATUS_OK, PrepareForSync(file_system1.file_system_context(), |
| 306 kURL1, &metadata, &changes)); | 310 kURL1, &metadata, &changes)); |
| 307 EXPECT_EQ(1U, changes.size()); | 311 EXPECT_EQ(1U, changes.size()); |
| 308 EXPECT_TRUE(changes.list().back().IsFile()); | 312 EXPECT_TRUE(changes.list().back().IsFile()); |
| 309 EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); | 313 EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| 310 EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type); | 314 EXPECT_EQ(sync_file_system::SYNC_FILE_TYPE_FILE, metadata.file_type); |
| 311 EXPECT_EQ(0, metadata.size); | 315 EXPECT_EQ(0, metadata.size); |
| 312 | 316 |
| 313 changes.clear(); | 317 changes.clear(); |
| 314 EXPECT_EQ(SYNC_STATUS_OK, PrepareForSync(file_system2.file_system_context(), | 318 EXPECT_EQ(SYNC_STATUS_OK, PrepareForSync(file_system2.file_system_context(), |
| 315 kURL2, &metadata, &changes)); | 319 kURL2, &metadata, &changes)); |
| 316 EXPECT_EQ(1U, changes.size()); | 320 EXPECT_EQ(1U, changes.size()); |
| 317 EXPECT_FALSE(changes.list().back().IsFile()); | 321 EXPECT_FALSE(changes.list().back().IsFile()); |
| 318 EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); | 322 EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| 319 EXPECT_EQ(SYNC_FILE_TYPE_DIRECTORY, metadata.file_type); | 323 EXPECT_EQ(sync_file_system::SYNC_FILE_TYPE_DIRECTORY, metadata.file_type); |
| 320 EXPECT_EQ(0, metadata.size); | 324 EXPECT_EQ(0, metadata.size); |
| 321 | 325 |
| 322 sync_context_->ShutdownOnUIThread(); | 326 sync_context_->ShutdownOnUIThread(); |
| 323 sync_context_ = NULL; | 327 sync_context_ = NULL; |
| 324 | 328 |
| 325 file_system1.TearDown(); | 329 file_system1.TearDown(); |
| 326 file_system2.TearDown(); | 330 file_system2.TearDown(); |
| 327 } | 331 } |
| 328 | 332 |
| 329 TEST_F(LocalFileSyncContextTest, PrepareSyncWhileWriting) { | 333 TEST_F(LocalFileSyncContextTest, PrepareSyncWhileWriting) { |
| 330 CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName, | 334 CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName, |
| 331 io_task_runner_, file_task_runner_); | 335 io_task_runner_, file_task_runner_); |
| 332 file_system.SetUp(); | 336 file_system.SetUp(); |
| 333 sync_context_ = new LocalFileSyncContext(ui_task_runner_, io_task_runner_); | 337 sync_context_ = new LocalFileSyncContext(ui_task_runner_, io_task_runner_); |
| 334 EXPECT_EQ(SYNC_STATUS_OK, | 338 EXPECT_EQ(SYNC_STATUS_OK, |
| 335 file_system.MaybeInitializeFileSystemContext(sync_context_)); | 339 file_system.MaybeInitializeFileSystemContext(sync_context_)); |
| 336 | 340 |
| 337 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); | 341 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.OpenFileSystem()); |
| 338 | 342 |
| 339 const FileSystemURL kURL1(file_system.URL("foo")); | 343 const FileSystemURL kURL1(file_system.URL("foo")); |
| 340 | 344 |
| 341 // Creates a file in file_system. | 345 // Creates a file in file_system. |
| 342 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kURL1)); | 346 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.CreateFile(kURL1)); |
| 343 | 347 |
| 344 // Kick file write on IO thread. | 348 // Kick file write on IO thread. |
| 345 StartModifyFileOnIOThread(&file_system, kURL1); | 349 StartModifyFileOnIOThread(&file_system, kURL1); |
| 346 | 350 |
| 347 // Until the operation finishes PrepareForSync should return BUSY error. | 351 // Until the operation finishes PrepareForSync should return BUSY error. |
| 348 SyncFileMetadata metadata; | 352 SyncFileMetadata metadata; |
| 349 metadata.file_type = SYNC_FILE_TYPE_UNKNOWN; | 353 metadata.file_type = sync_file_system::SYNC_FILE_TYPE_UNKNOWN; |
| 350 FileChangeList changes; | 354 FileChangeList changes; |
| 351 EXPECT_EQ(SYNC_STATUS_FILE_BUSY, | 355 EXPECT_EQ(SYNC_STATUS_FILE_BUSY, |
| 352 PrepareForSync(file_system.file_system_context(), | 356 PrepareForSync(file_system.file_system_context(), |
| 353 kURL1, &metadata, &changes)); | 357 kURL1, &metadata, &changes)); |
| 354 EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type); | 358 EXPECT_EQ(sync_file_system::SYNC_FILE_TYPE_FILE, metadata.file_type); |
| 355 | 359 |
| 356 // Register PrepareForSync method to be invoked when kURL1 becomes | 360 // Register PrepareForSync method to be invoked when kURL1 becomes |
| 357 // syncable. (Actually this may be done after all operations are done | 361 // syncable. (Actually this may be done after all operations are done |
| 358 // on IO thread in this test.) | 362 // on IO thread in this test.) |
| 359 metadata.file_type = SYNC_FILE_TYPE_UNKNOWN; | 363 metadata.file_type = sync_file_system::SYNC_FILE_TYPE_UNKNOWN; |
| 360 changes.clear(); | 364 changes.clear(); |
| 361 sync_context_->RegisterURLForWaitingSync( | 365 sync_context_->RegisterURLForWaitingSync( |
| 362 kURL1, GetPrepareForSyncClosure(file_system.file_system_context(), | 366 kURL1, GetPrepareForSyncClosure(file_system.file_system_context(), |
| 363 kURL1, &metadata, &changes)); | 367 kURL1, &metadata, &changes)); |
| 364 | 368 |
| 365 // Wait for the completion. | 369 // Wait for the completion. |
| 366 EXPECT_EQ(base::PLATFORM_FILE_OK, WaitUntilModifyFileIsDone()); | 370 EXPECT_EQ(base::PLATFORM_FILE_OK, WaitUntilModifyFileIsDone()); |
| 367 | 371 |
| 368 // The PrepareForSync must have been started; wait until DidPrepareForSync | 372 // The PrepareForSync must have been started; wait until DidPrepareForSync |
| 369 // is done. | 373 // is done. |
| 370 MessageLoop::current()->Run(); | 374 MessageLoop::current()->Run(); |
| 371 ASSERT_FALSE(has_inflight_prepare_for_sync_); | 375 ASSERT_FALSE(has_inflight_prepare_for_sync_); |
| 372 | 376 |
| 373 // Now PrepareForSync should have run and returned OK. | 377 // Now PrepareForSync should have run and returned OK. |
| 374 EXPECT_EQ(SYNC_STATUS_OK, status_); | 378 EXPECT_EQ(SYNC_STATUS_OK, status_); |
| 375 EXPECT_EQ(1U, changes.size()); | 379 EXPECT_EQ(1U, changes.size()); |
| 376 EXPECT_TRUE(changes.list().back().IsFile()); | 380 EXPECT_TRUE(changes.list().back().IsFile()); |
| 377 EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); | 381 EXPECT_TRUE(changes.list().back().IsAddOrUpdate()); |
| 378 EXPECT_EQ(SYNC_FILE_TYPE_FILE, metadata.file_type); | 382 EXPECT_EQ(sync_file_system::SYNC_FILE_TYPE_FILE, metadata.file_type); |
| 379 EXPECT_EQ(1, metadata.size); | 383 EXPECT_EQ(1, metadata.size); |
| 380 | 384 |
| 381 sync_context_->ShutdownOnUIThread(); | 385 sync_context_->ShutdownOnUIThread(); |
| 382 sync_context_ = NULL; | 386 sync_context_ = NULL; |
| 383 file_system.TearDown(); | 387 file_system.TearDown(); |
| 384 } | 388 } |
| 385 | 389 |
| 386 TEST_F(LocalFileSyncContextTest, ApplyRemoteChangeForDeletion) { | 390 TEST_F(LocalFileSyncContextTest, ApplyRemoteChangeForDeletion) { |
| 387 CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName, | 391 CannedSyncableFileSystem file_system(GURL(kOrigin1), kServiceName, |
| 388 io_task_runner_, file_task_runner_); | 392 io_task_runner_, file_task_runner_); |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 421 } | 425 } |
| 422 | 426 |
| 423 // At this point the usage must be greater than the initial usage. | 427 // At this point the usage must be greater than the initial usage. |
| 424 int64 new_usage = -1; | 428 int64 new_usage = -1; |
| 425 EXPECT_EQ(quota::kQuotaStatusOk, | 429 EXPECT_EQ(quota::kQuotaStatusOk, |
| 426 file_system.GetUsageAndQuota(&new_usage, "a)); | 430 file_system.GetUsageAndQuota(&new_usage, "a)); |
| 427 EXPECT_GT(new_usage, initial_usage); | 431 EXPECT_GT(new_usage, initial_usage); |
| 428 | 432 |
| 429 // Now let's apply remote deletion changes. | 433 // Now let's apply remote deletion changes. |
| 430 FileChange change(FileChange::FILE_CHANGE_DELETE, | 434 FileChange change(FileChange::FILE_CHANGE_DELETE, |
| 431 SYNC_FILE_TYPE_FILE); | 435 sync_file_system::SYNC_FILE_TYPE_FILE); |
| 432 EXPECT_EQ(SYNC_STATUS_OK, | 436 EXPECT_EQ(SYNC_STATUS_OK, |
| 433 ApplyRemoteChange(file_system.file_system_context(), | 437 ApplyRemoteChange(file_system.file_system_context(), |
| 434 change, base::FilePath(), kFile, | 438 change, base::FilePath(), kFile, |
| 435 SYNC_FILE_TYPE_FILE)); | 439 sync_file_system::SYNC_FILE_TYPE_FILE)); |
| 436 | 440 |
| 437 // The implementation doesn't check file type for deletion, and it must be ok | 441 // The implementation doesn't check file type for deletion, and it must be ok |
| 438 // even if we don't know if the deletion change was for a file or a directory. | 442 // even if we don't know if the deletion change was for a file or a directory. |
| 439 change = FileChange(FileChange::FILE_CHANGE_DELETE, SYNC_FILE_TYPE_UNKNOWN); | 443 change = FileChange(FileChange::FILE_CHANGE_DELETE, |
| 444 sync_file_system::SYNC_FILE_TYPE_UNKNOWN); |
| 440 EXPECT_EQ(SYNC_STATUS_OK, | 445 EXPECT_EQ(SYNC_STATUS_OK, |
| 441 ApplyRemoteChange(file_system.file_system_context(), | 446 ApplyRemoteChange(file_system.file_system_context(), |
| 442 change, base::FilePath(), kDir, | 447 change, base::FilePath(), kDir, |
| 443 SYNC_FILE_TYPE_DIRECTORY)); | 448 sync_file_system::SYNC_FILE_TYPE_DIRECTORY)); |
| 444 | 449 |
| 445 // Check the directory/files are deleted successfully. | 450 // Check the directory/files are deleted successfully. |
| 446 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, | 451 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, |
| 447 file_system.FileExists(kFile)); | 452 file_system.FileExists(kFile)); |
| 448 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, | 453 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, |
| 449 file_system.DirectoryExists(kDir)); | 454 file_system.DirectoryExists(kDir)); |
| 450 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, | 455 EXPECT_EQ(base::PLATFORM_FILE_ERROR_NOT_FOUND, |
| 451 file_system.FileExists(kChild)); | 456 file_system.FileExists(kChild)); |
| 452 | 457 |
| 453 // The changes applied by ApplyRemoteChange should not be recorded in | 458 // The changes applied by ApplyRemoteChange should not be recorded in |
| (...skipping 76 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 530 // * kFile2 with kTestFileData2 | 535 // * kFile2 with kTestFileData2 |
| 531 // * kDir | 536 // * kDir |
| 532 // | 537 // |
| 533 // By calling ApplyChange's: | 538 // By calling ApplyChange's: |
| 534 // * kFile1 will be updated to have kTestFileData1 | 539 // * kFile1 will be updated to have kTestFileData1 |
| 535 // * kFile2 will be created | 540 // * kFile2 will be created |
| 536 // * kDir will be created | 541 // * kDir will be created |
| 537 | 542 |
| 538 // Apply the remote change to kFile1 (which will update the file). | 543 // Apply the remote change to kFile1 (which will update the file). |
| 539 FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE, | 544 FileChange change(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| 540 SYNC_FILE_TYPE_FILE); | 545 sync_file_system::SYNC_FILE_TYPE_FILE); |
| 541 EXPECT_EQ(SYNC_STATUS_OK, | 546 EXPECT_EQ(SYNC_STATUS_OK, |
| 542 ApplyRemoteChange(file_system.file_system_context(), | 547 ApplyRemoteChange(file_system.file_system_context(), |
| 543 change, kFilePath1, kFile1, | 548 change, kFilePath1, kFile1, |
| 544 SYNC_FILE_TYPE_FILE)); | 549 sync_file_system::SYNC_FILE_TYPE_FILE)); |
| 545 | 550 |
| 546 // Check if the usage has been increased by (kTestFileData1 - kTestFileData0). | 551 // Check if the usage has been increased by (kTestFileData1 - kTestFileData0). |
| 547 const int updated_size = | 552 const int updated_size = |
| 548 arraysize(kTestFileData1) - arraysize(kTestFileData0); | 553 arraysize(kTestFileData1) - arraysize(kTestFileData0); |
| 549 EXPECT_EQ(quota::kQuotaStatusOk, | 554 EXPECT_EQ(quota::kQuotaStatusOk, |
| 550 file_system.GetUsageAndQuota(&new_usage, "a)); | 555 file_system.GetUsageAndQuota(&new_usage, "a)); |
| 551 EXPECT_EQ(updated_size, new_usage - usage); | 556 EXPECT_EQ(updated_size, new_usage - usage); |
| 552 | 557 |
| 553 // Apply remote changes to kFile2 and kDir (should create a file and | 558 // Apply remote changes to kFile2 and kDir (should create a file and |
| 554 // directory respectively). | 559 // directory respectively). |
| 555 // They are non-existent yet so their expected file type (the last | 560 // They are non-existent yet so their expected file type (the last |
| 556 // parameter of ApplyRemoteChange) are SYNC_FILE_TYPE_UNKNOWN. | 561 // parameter of ApplyRemoteChange) are |
| 562 // sync_file_system::SYNC_FILE_TYPE_UNKNOWN. |
| 557 change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, | 563 change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| 558 SYNC_FILE_TYPE_FILE); | 564 SYNC_FILE_TYPE_FILE); |
| 559 EXPECT_EQ(SYNC_STATUS_OK, | 565 EXPECT_EQ(SYNC_STATUS_OK, |
| 560 ApplyRemoteChange(file_system.file_system_context(), | 566 ApplyRemoteChange(file_system.file_system_context(), |
| 561 change, kFilePath2, kFile2, | 567 change, kFilePath2, kFile2, |
| 562 SYNC_FILE_TYPE_UNKNOWN)); | 568 sync_file_system::SYNC_FILE_TYPE_UNKNOWN)); |
| 563 | 569 |
| 564 change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, | 570 change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| 565 SYNC_FILE_TYPE_DIRECTORY); | 571 sync_file_system::SYNC_FILE_TYPE_DIRECTORY); |
| 566 EXPECT_EQ(SYNC_STATUS_OK, | 572 EXPECT_EQ(SYNC_STATUS_OK, |
| 567 ApplyRemoteChange(file_system.file_system_context(), | 573 ApplyRemoteChange(file_system.file_system_context(), |
| 568 change, base::FilePath(), kDir, | 574 change, base::FilePath(), kDir, |
| 569 SYNC_FILE_TYPE_UNKNOWN)); | 575 sync_file_system::SYNC_FILE_TYPE_UNKNOWN)); |
| 570 | 576 |
| 571 // This should not happen, but calling ApplyRemoteChange | 577 // This should not happen, but calling ApplyRemoteChange |
| 572 // with wrong file type will result in error. | 578 // with wrong file type will result in error. |
| 573 change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, | 579 change = FileChange(FileChange::FILE_CHANGE_ADD_OR_UPDATE, |
| 574 SYNC_FILE_TYPE_FILE); | 580 SYNC_FILE_TYPE_FILE); |
| 575 EXPECT_NE(SYNC_STATUS_OK, | 581 EXPECT_NE(SYNC_STATUS_OK, |
| 576 ApplyRemoteChange(file_system.file_system_context(), | 582 ApplyRemoteChange(file_system.file_system_context(), |
| 577 change, kFilePath1, kDir, | 583 change, kFilePath1, kDir, |
| 578 SYNC_FILE_TYPE_DIRECTORY)); | 584 sync_file_system::SYNC_FILE_TYPE_DIRECTORY)); |
| 579 | 585 |
| 580 // Creating a file/directory must have increased the usage more than | 586 // Creating a file/directory must have increased the usage more than |
| 581 // the size of kTestFileData2. | 587 // the size of kTestFileData2. |
| 582 new_usage = usage; | 588 new_usage = usage; |
| 583 EXPECT_EQ(quota::kQuotaStatusOk, | 589 EXPECT_EQ(quota::kQuotaStatusOk, |
| 584 file_system.GetUsageAndQuota(&new_usage, "a)); | 590 file_system.GetUsageAndQuota(&new_usage, "a)); |
| 585 EXPECT_GT(new_usage, | 591 EXPECT_GT(new_usage, |
| 586 static_cast<int64>(usage + arraysize(kTestFileData2) - 1)); | 592 static_cast<int64>(usage + arraysize(kTestFileData2) - 1)); |
| 587 | 593 |
| 588 // The changes applied by ApplyRemoteChange should not be recorded in | 594 // The changes applied by ApplyRemoteChange should not be recorded in |
| 589 // the change tracker. | 595 // the change tracker. |
| 590 urls.clear(); | 596 urls.clear(); |
| 591 file_system.GetChangedURLsInTracker(&urls); | 597 file_system.GetChangedURLsInTracker(&urls); |
| 592 EXPECT_TRUE(urls.empty()); | 598 EXPECT_TRUE(urls.empty()); |
| 593 | 599 |
| 594 // Make sure all three files/directory exist. | 600 // Make sure all three files/directory exist. |
| 595 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile1)); | 601 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile1)); |
| 596 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile2)); | 602 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.FileExists(kFile2)); |
| 597 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.DirectoryExists(kDir)); | 603 EXPECT_EQ(base::PLATFORM_FILE_OK, file_system.DirectoryExists(kDir)); |
| 598 | 604 |
| 599 sync_context_->ShutdownOnUIThread(); | 605 sync_context_->ShutdownOnUIThread(); |
| 600 file_system.TearDown(); | 606 file_system.TearDown(); |
| 601 } | 607 } |
| 602 | 608 |
| 603 } // namespace fileapi | 609 } // namespace fileapi |
| OLD | NEW |