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 "google_apis/drive/base_requests.h" | 5 #include "google_apis/drive/base_requests.h" |
6 | 6 |
| 7 #include <stddef.h> |
| 8 |
7 #include "base/files/file_util.h" | 9 #include "base/files/file_util.h" |
8 #include "base/json/json_reader.h" | 10 #include "base/json/json_reader.h" |
9 #include "base/json/json_writer.h" | 11 #include "base/json/json_writer.h" |
10 #include "base/location.h" | 12 #include "base/location.h" |
| 13 #include "base/macros.h" |
11 #include "base/rand_util.h" | 14 #include "base/rand_util.h" |
12 #include "base/sequenced_task_runner.h" | 15 #include "base/sequenced_task_runner.h" |
13 #include "base/strings/string_number_conversions.h" | 16 #include "base/strings/string_number_conversions.h" |
14 #include "base/strings/stringprintf.h" | 17 #include "base/strings/stringprintf.h" |
15 #include "base/task_runner_util.h" | 18 #include "base/task_runner_util.h" |
16 #include "base/thread_task_runner_handle.h" | 19 #include "base/thread_task_runner_handle.h" |
17 #include "base/values.h" | 20 #include "base/values.h" |
18 #include "google_apis/drive/drive_api_parser.h" | 21 #include "google_apis/drive/drive_api_parser.h" |
19 #include "google_apis/drive/request_sender.h" | 22 #include "google_apis/drive/request_sender.h" |
20 #include "google_apis/drive/request_util.h" | 23 #include "google_apis/drive/request_util.h" |
(...skipping 190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
211 LOG(WARNING) << "Error while parsing entry response: " << error_message | 214 LOG(WARNING) << "Error while parsing entry response: " << error_message |
212 << ", code: " << error_code << ", json:\n" << trimmed_json; | 215 << ", code: " << error_code << ", json:\n" << trimmed_json; |
213 } | 216 } |
214 return value.Pass(); | 217 return value.Pass(); |
215 } | 218 } |
216 | 219 |
217 void GenerateMultipartBody(MultipartType multipart_type, | 220 void GenerateMultipartBody(MultipartType multipart_type, |
218 const std::string& predetermined_boundary, | 221 const std::string& predetermined_boundary, |
219 const std::vector<ContentTypeAndData>& parts, | 222 const std::vector<ContentTypeAndData>& parts, |
220 ContentTypeAndData* output, | 223 ContentTypeAndData* output, |
221 std::vector<uint64>* data_offset) { | 224 std::vector<uint64_t>* data_offset) { |
222 std::string boundary; | 225 std::string boundary; |
223 // Generate random boundary. | 226 // Generate random boundary. |
224 if (predetermined_boundary.empty()) { | 227 if (predetermined_boundary.empty()) { |
225 while (true) { | 228 while (true) { |
226 boundary.resize(kBoundarySize); | 229 boundary.resize(kBoundarySize); |
227 for (int i = 0; i < kBoundarySize; ++i) { | 230 for (int i = 0; i < kBoundarySize; ++i) { |
228 // Subtract 2 from the array size to exclude '\0', and to turn the size | 231 // Subtract 2 from the array size to exclude '\0', and to turn the size |
229 // into the last index. | 232 // into the last index. |
230 const int last_char_index = arraysize(kBoundaryCharacters) - 2; | 233 const int last_char_index = arraysize(kBoundaryCharacters) - 2; |
231 boundary[i] = kBoundaryCharacters[base::RandInt(0, last_char_index)]; | 234 boundary[i] = kBoundaryCharacters[base::RandInt(0, last_char_index)]; |
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
443 DVLOG(1) << "Extra header: " << headers[i]; | 446 DVLOG(1) << "Extra header: " << headers[i]; |
444 } | 447 } |
445 | 448 |
446 // Set upload data if available. | 449 // Set upload data if available. |
447 std::string upload_content_type; | 450 std::string upload_content_type; |
448 std::string upload_content; | 451 std::string upload_content; |
449 if (GetContentData(&upload_content_type, &upload_content)) { | 452 if (GetContentData(&upload_content_type, &upload_content)) { |
450 url_fetcher_->SetUploadData(upload_content_type, upload_content); | 453 url_fetcher_->SetUploadData(upload_content_type, upload_content); |
451 } else { | 454 } else { |
452 base::FilePath local_file_path; | 455 base::FilePath local_file_path; |
453 int64 range_offset = 0; | 456 int64_t range_offset = 0; |
454 int64 range_length = 0; | 457 int64_t range_length = 0; |
455 if (GetContentFile(&local_file_path, &range_offset, &range_length, | 458 if (GetContentFile(&local_file_path, &range_offset, &range_length, |
456 &upload_content_type)) { | 459 &upload_content_type)) { |
457 url_fetcher_->SetUploadFilePath( | 460 url_fetcher_->SetUploadFilePath( |
458 upload_content_type, | 461 upload_content_type, |
459 local_file_path, | 462 local_file_path, |
460 range_offset, | 463 range_offset, |
461 range_length, | 464 range_length, |
462 blocking_task_runner()); | 465 blocking_task_runner()); |
463 } else { | 466 } else { |
464 // Even if there is no content data, UrlFetcher requires to set empty | 467 // Even if there is no content data, UrlFetcher requires to set empty |
(...skipping 20 matching lines...) Expand all Loading... |
485 std::vector<std::string> UrlFetchRequestBase::GetExtraRequestHeaders() const { | 488 std::vector<std::string> UrlFetchRequestBase::GetExtraRequestHeaders() const { |
486 return std::vector<std::string>(); | 489 return std::vector<std::string>(); |
487 } | 490 } |
488 | 491 |
489 bool UrlFetchRequestBase::GetContentData(std::string* upload_content_type, | 492 bool UrlFetchRequestBase::GetContentData(std::string* upload_content_type, |
490 std::string* upload_content) { | 493 std::string* upload_content) { |
491 return false; | 494 return false; |
492 } | 495 } |
493 | 496 |
494 bool UrlFetchRequestBase::GetContentFile(base::FilePath* local_file_path, | 497 bool UrlFetchRequestBase::GetContentFile(base::FilePath* local_file_path, |
495 int64* range_offset, | 498 int64_t* range_offset, |
496 int64* range_length, | 499 int64_t* range_length, |
497 std::string* upload_content_type) { | 500 std::string* upload_content_type) { |
498 return false; | 501 return false; |
499 } | 502 } |
500 | 503 |
501 void UrlFetchRequestBase::GetOutputFilePath( | 504 void UrlFetchRequestBase::GetOutputFilePath( |
502 base::FilePath* local_file_path, | 505 base::FilePath* local_file_path, |
503 GetContentCallback* get_content_callback) { | 506 GetContentCallback* get_content_callback) { |
504 } | 507 } |
505 | 508 |
506 void UrlFetchRequestBase::Cancel() { | 509 void UrlFetchRequestBase::Cancel() { |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
593 void EntryActionRequest::RunCallbackOnPrematureFailure(DriveApiErrorCode code) { | 596 void EntryActionRequest::RunCallbackOnPrematureFailure(DriveApiErrorCode code) { |
594 callback_.Run(code); | 597 callback_.Run(code); |
595 } | 598 } |
596 | 599 |
597 //========================= InitiateUploadRequestBase ======================== | 600 //========================= InitiateUploadRequestBase ======================== |
598 | 601 |
599 InitiateUploadRequestBase::InitiateUploadRequestBase( | 602 InitiateUploadRequestBase::InitiateUploadRequestBase( |
600 RequestSender* sender, | 603 RequestSender* sender, |
601 const InitiateUploadCallback& callback, | 604 const InitiateUploadCallback& callback, |
602 const std::string& content_type, | 605 const std::string& content_type, |
603 int64 content_length) | 606 int64_t content_length) |
604 : UrlFetchRequestBase(sender), | 607 : UrlFetchRequestBase(sender), |
605 callback_(callback), | 608 callback_(callback), |
606 content_type_(content_type), | 609 content_type_(content_type), |
607 content_length_(content_length) { | 610 content_length_(content_length) { |
608 DCHECK(!callback_.is_null()); | 611 DCHECK(!callback_.is_null()); |
609 DCHECK(!content_type_.empty()); | 612 DCHECK(!content_type_.empty()); |
610 DCHECK_GE(content_length_, 0); | 613 DCHECK_GE(content_length_, 0); |
611 } | 614 } |
612 | 615 |
613 InitiateUploadRequestBase::~InitiateUploadRequestBase() {} | 616 InitiateUploadRequestBase::~InitiateUploadRequestBase() {} |
(...skipping 30 matching lines...) Expand all Loading... |
644 | 647 |
645 //============================ UploadRangeResponse ============================= | 648 //============================ UploadRangeResponse ============================= |
646 | 649 |
647 UploadRangeResponse::UploadRangeResponse() | 650 UploadRangeResponse::UploadRangeResponse() |
648 : code(HTTP_SUCCESS), | 651 : code(HTTP_SUCCESS), |
649 start_position_received(0), | 652 start_position_received(0), |
650 end_position_received(0) { | 653 end_position_received(0) { |
651 } | 654 } |
652 | 655 |
653 UploadRangeResponse::UploadRangeResponse(DriveApiErrorCode code, | 656 UploadRangeResponse::UploadRangeResponse(DriveApiErrorCode code, |
654 int64 start_position_received, | 657 int64_t start_position_received, |
655 int64 end_position_received) | 658 int64_t end_position_received) |
656 : code(code), | 659 : code(code), |
657 start_position_received(start_position_received), | 660 start_position_received(start_position_received), |
658 end_position_received(end_position_received) { | 661 end_position_received(end_position_received) {} |
659 } | |
660 | 662 |
661 UploadRangeResponse::~UploadRangeResponse() { | 663 UploadRangeResponse::~UploadRangeResponse() { |
662 } | 664 } |
663 | 665 |
664 //========================== UploadRangeRequestBase ========================== | 666 //========================== UploadRangeRequestBase ========================== |
665 | 667 |
666 UploadRangeRequestBase::UploadRangeRequestBase(RequestSender* sender, | 668 UploadRangeRequestBase::UploadRangeRequestBase(RequestSender* sender, |
667 const GURL& upload_url) | 669 const GURL& upload_url) |
668 : UrlFetchRequestBase(sender), | 670 : UrlFetchRequestBase(sender), |
669 upload_url_(upload_url), | 671 upload_url_(upload_url), |
(...skipping 15 matching lines...) Expand all Loading... |
685 void UploadRangeRequestBase::ProcessURLFetchResults( | 687 void UploadRangeRequestBase::ProcessURLFetchResults( |
686 const URLFetcher* source) { | 688 const URLFetcher* source) { |
687 DriveApiErrorCode code = GetErrorCode(); | 689 DriveApiErrorCode code = GetErrorCode(); |
688 net::HttpResponseHeaders* hdrs = source->GetResponseHeaders(); | 690 net::HttpResponseHeaders* hdrs = source->GetResponseHeaders(); |
689 | 691 |
690 if (code == HTTP_RESUME_INCOMPLETE) { | 692 if (code == HTTP_RESUME_INCOMPLETE) { |
691 // Retrieve value of the first "Range" header. | 693 // Retrieve value of the first "Range" header. |
692 // The Range header is appeared only if there is at least one received | 694 // The Range header is appeared only if there is at least one received |
693 // byte. So, initialize the positions by 0 so that the [0,0) will be | 695 // byte. So, initialize the positions by 0 so that the [0,0) will be |
694 // returned via the |callback_| for empty data case. | 696 // returned via the |callback_| for empty data case. |
695 int64 start_position_received = 0; | 697 int64_t start_position_received = 0; |
696 int64 end_position_received = 0; | 698 int64_t end_position_received = 0; |
697 std::string range_received; | 699 std::string range_received; |
698 hdrs->EnumerateHeader(NULL, kUploadResponseRange, &range_received); | 700 hdrs->EnumerateHeader(NULL, kUploadResponseRange, &range_received); |
699 if (!range_received.empty()) { // Parse the range header. | 701 if (!range_received.empty()) { // Parse the range header. |
700 std::vector<net::HttpByteRange> ranges; | 702 std::vector<net::HttpByteRange> ranges; |
701 if (net::HttpUtil::ParseRangeHeader(range_received, &ranges) && | 703 if (net::HttpUtil::ParseRangeHeader(range_received, &ranges) && |
702 !ranges.empty() ) { | 704 !ranges.empty() ) { |
703 // We only care about the first start-end pair in the range. | 705 // We only care about the first start-end pair in the range. |
704 // | 706 // |
705 // Range header represents the range inclusively, while we are treating | 707 // Range header represents the range inclusively, while we are treating |
706 // ranges exclusively (i.e., end_position_received should be one passed | 708 // ranges exclusively (i.e., end_position_received should be one passed |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
748 DriveApiErrorCode code) { | 750 DriveApiErrorCode code) { |
749 OnRangeRequestComplete( | 751 OnRangeRequestComplete( |
750 UploadRangeResponse(code, 0, 0), scoped_ptr<base::Value>()); | 752 UploadRangeResponse(code, 0, 0), scoped_ptr<base::Value>()); |
751 } | 753 } |
752 | 754 |
753 //========================== ResumeUploadRequestBase ========================= | 755 //========================== ResumeUploadRequestBase ========================= |
754 | 756 |
755 ResumeUploadRequestBase::ResumeUploadRequestBase( | 757 ResumeUploadRequestBase::ResumeUploadRequestBase( |
756 RequestSender* sender, | 758 RequestSender* sender, |
757 const GURL& upload_location, | 759 const GURL& upload_location, |
758 int64 start_position, | 760 int64_t start_position, |
759 int64 end_position, | 761 int64_t end_position, |
760 int64 content_length, | 762 int64_t content_length, |
761 const std::string& content_type, | 763 const std::string& content_type, |
762 const base::FilePath& local_file_path) | 764 const base::FilePath& local_file_path) |
763 : UploadRangeRequestBase(sender, upload_location), | 765 : UploadRangeRequestBase(sender, upload_location), |
764 start_position_(start_position), | 766 start_position_(start_position), |
765 end_position_(end_position), | 767 end_position_(end_position), |
766 content_length_(content_length), | 768 content_length_(content_length), |
767 content_type_(content_type), | 769 content_type_(content_type), |
768 local_file_path_(local_file_path) { | 770 local_file_path_(local_file_path) { |
769 DCHECK_LE(start_position_, end_position_); | 771 DCHECK_LE(start_position_, end_position_); |
770 } | 772 } |
(...skipping 20 matching lines...) Expand all Loading... |
791 | 793 |
792 std::vector<std::string> headers; | 794 std::vector<std::string> headers; |
793 headers.push_back( | 795 headers.push_back( |
794 std::string(kUploadContentRange) + | 796 std::string(kUploadContentRange) + |
795 base::Int64ToString(start_position_) + "-" + | 797 base::Int64ToString(start_position_) + "-" + |
796 base::Int64ToString(end_position_ - 1) + "/" + | 798 base::Int64ToString(end_position_ - 1) + "/" + |
797 base::Int64ToString(content_length_)); | 799 base::Int64ToString(content_length_)); |
798 return headers; | 800 return headers; |
799 } | 801 } |
800 | 802 |
801 bool ResumeUploadRequestBase::GetContentFile( | 803 bool ResumeUploadRequestBase::GetContentFile(base::FilePath* local_file_path, |
802 base::FilePath* local_file_path, | 804 int64_t* range_offset, |
803 int64* range_offset, | 805 int64_t* range_length, |
804 int64* range_length, | 806 std::string* upload_content_type) { |
805 std::string* upload_content_type) { | |
806 if (start_position_ == end_position_) { | 807 if (start_position_ == end_position_) { |
807 // No content data. | 808 // No content data. |
808 return false; | 809 return false; |
809 } | 810 } |
810 | 811 |
811 *local_file_path = local_file_path_; | 812 *local_file_path = local_file_path_; |
812 *range_offset = start_position_; | 813 *range_offset = start_position_; |
813 *range_length = end_position_ - start_position_; | 814 *range_length = end_position_ - start_position_; |
814 *upload_content_type = content_type_; | 815 *upload_content_type = content_type_; |
815 return true; | 816 return true; |
816 } | 817 } |
817 | 818 |
818 //======================== GetUploadStatusRequestBase ======================== | 819 //======================== GetUploadStatusRequestBase ======================== |
819 | 820 |
820 GetUploadStatusRequestBase::GetUploadStatusRequestBase(RequestSender* sender, | 821 GetUploadStatusRequestBase::GetUploadStatusRequestBase(RequestSender* sender, |
821 const GURL& upload_url, | 822 const GURL& upload_url, |
822 int64 content_length) | 823 int64_t content_length) |
823 : UploadRangeRequestBase(sender, upload_url), | 824 : UploadRangeRequestBase(sender, upload_url), |
824 content_length_(content_length) {} | 825 content_length_(content_length) {} |
825 | 826 |
826 GetUploadStatusRequestBase::~GetUploadStatusRequestBase() {} | 827 GetUploadStatusRequestBase::~GetUploadStatusRequestBase() {} |
827 | 828 |
828 std::vector<std::string> | 829 std::vector<std::string> |
829 GetUploadStatusRequestBase::GetExtraRequestHeaders() const { | 830 GetUploadStatusRequestBase::GetExtraRequestHeaders() const { |
830 // The header looks like | 831 // The header looks like |
831 // Content-Range: bytes */<content_length> | 832 // Content-Range: bytes */<content_length> |
832 // for example: | 833 // for example: |
833 // Content-Range: bytes */13851821 | 834 // Content-Range: bytes */13851821 |
834 DCHECK_GE(content_length_, 0); | 835 DCHECK_GE(content_length_, 0); |
835 | 836 |
836 std::vector<std::string> headers; | 837 std::vector<std::string> headers; |
837 headers.push_back( | 838 headers.push_back( |
838 std::string(kUploadContentRange) + "*/" + | 839 std::string(kUploadContentRange) + "*/" + |
839 base::Int64ToString(content_length_)); | 840 base::Int64ToString(content_length_)); |
840 return headers; | 841 return headers; |
841 } | 842 } |
842 | 843 |
843 //========================= MultipartUploadRequestBase ======================== | 844 //========================= MultipartUploadRequestBase ======================== |
844 | 845 |
845 MultipartUploadRequestBase::MultipartUploadRequestBase( | 846 MultipartUploadRequestBase::MultipartUploadRequestBase( |
846 base::SequencedTaskRunner* blocking_task_runner, | 847 base::SequencedTaskRunner* blocking_task_runner, |
847 const std::string& metadata_json, | 848 const std::string& metadata_json, |
848 const std::string& content_type, | 849 const std::string& content_type, |
849 int64 content_length, | 850 int64_t content_length, |
850 const base::FilePath& local_file_path, | 851 const base::FilePath& local_file_path, |
851 const FileResourceCallback& callback, | 852 const FileResourceCallback& callback, |
852 const ProgressCallback& progress_callback) | 853 const ProgressCallback& progress_callback) |
853 : blocking_task_runner_(blocking_task_runner), | 854 : blocking_task_runner_(blocking_task_runner), |
854 metadata_json_(metadata_json), | 855 metadata_json_(metadata_json), |
855 content_type_(content_type), | 856 content_type_(content_type), |
856 local_path_(local_file_path), | 857 local_path_(local_file_path), |
857 callback_(callback), | 858 callback_(callback), |
858 progress_callback_(progress_callback), | 859 progress_callback_(progress_callback), |
859 weak_ptr_factory_(this) { | 860 weak_ptr_factory_(this) { |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
932 notify_complete_callback.Run(); | 933 notify_complete_callback.Run(); |
933 } | 934 } |
934 } | 935 } |
935 | 936 |
936 void MultipartUploadRequestBase::NotifyError(DriveApiErrorCode code) { | 937 void MultipartUploadRequestBase::NotifyError(DriveApiErrorCode code) { |
937 callback_.Run(code, scoped_ptr<FileResource>()); | 938 callback_.Run(code, scoped_ptr<FileResource>()); |
938 } | 939 } |
939 | 940 |
940 void MultipartUploadRequestBase::NotifyUploadProgress( | 941 void MultipartUploadRequestBase::NotifyUploadProgress( |
941 const net::URLFetcher* source, | 942 const net::URLFetcher* source, |
942 int64 current, | 943 int64_t current, |
943 int64 total) { | 944 int64_t total) { |
944 if (!progress_callback_.is_null()) | 945 if (!progress_callback_.is_null()) |
945 progress_callback_.Run(current, total); | 946 progress_callback_.Run(current, total); |
946 } | 947 } |
947 | 948 |
948 void MultipartUploadRequestBase::OnDataParsed( | 949 void MultipartUploadRequestBase::OnDataParsed( |
949 DriveApiErrorCode code, | 950 DriveApiErrorCode code, |
950 const base::Closure& notify_complete_callback, | 951 const base::Closure& notify_complete_callback, |
951 scoped_ptr<base::Value> value) { | 952 scoped_ptr<base::Value> value) { |
952 DCHECK(thread_checker_.CalledOnValidThread()); | 953 DCHECK(thread_checker_.CalledOnValidThread()); |
953 if (value) | 954 if (value) |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
987 void DownloadFileRequestBase::GetOutputFilePath( | 988 void DownloadFileRequestBase::GetOutputFilePath( |
988 base::FilePath* local_file_path, | 989 base::FilePath* local_file_path, |
989 GetContentCallback* get_content_callback) { | 990 GetContentCallback* get_content_callback) { |
990 // Configure so that the downloaded content is saved to |output_file_path_|. | 991 // Configure so that the downloaded content is saved to |output_file_path_|. |
991 *local_file_path = output_file_path_; | 992 *local_file_path = output_file_path_; |
992 *get_content_callback = get_content_callback_; | 993 *get_content_callback = get_content_callback_; |
993 } | 994 } |
994 | 995 |
995 void DownloadFileRequestBase::OnURLFetchDownloadProgress( | 996 void DownloadFileRequestBase::OnURLFetchDownloadProgress( |
996 const URLFetcher* source, | 997 const URLFetcher* source, |
997 int64 current, | 998 int64_t current, |
998 int64 total) { | 999 int64_t total) { |
999 if (!progress_callback_.is_null()) | 1000 if (!progress_callback_.is_null()) |
1000 progress_callback_.Run(current, total); | 1001 progress_callback_.Run(current, total); |
1001 } | 1002 } |
1002 | 1003 |
1003 void DownloadFileRequestBase::ProcessURLFetchResults(const URLFetcher* source) { | 1004 void DownloadFileRequestBase::ProcessURLFetchResults(const URLFetcher* source) { |
1004 DriveApiErrorCode code = GetErrorCode(); | 1005 DriveApiErrorCode code = GetErrorCode(); |
1005 | 1006 |
1006 // Take over the ownership of the the downloaded temp file. | 1007 // Take over the ownership of the the downloaded temp file. |
1007 base::FilePath temp_file; | 1008 base::FilePath temp_file; |
1008 if (code == HTTP_SUCCESS) { | 1009 if (code == HTTP_SUCCESS) { |
1009 response_writer()->DisownFile(); | 1010 response_writer()->DisownFile(); |
1010 temp_file = output_file_path_; | 1011 temp_file = output_file_path_; |
1011 } | 1012 } |
1012 | 1013 |
1013 download_action_callback_.Run(code, temp_file); | 1014 download_action_callback_.Run(code, temp_file); |
1014 OnProcessURLFetchResultsComplete(); | 1015 OnProcessURLFetchResultsComplete(); |
1015 } | 1016 } |
1016 | 1017 |
1017 void DownloadFileRequestBase::RunCallbackOnPrematureFailure( | 1018 void DownloadFileRequestBase::RunCallbackOnPrematureFailure( |
1018 DriveApiErrorCode code) { | 1019 DriveApiErrorCode code) { |
1019 download_action_callback_.Run(code, base::FilePath()); | 1020 download_action_callback_.Run(code, base::FilePath()); |
1020 } | 1021 } |
1021 | 1022 |
1022 } // namespace google_apis | 1023 } // namespace google_apis |
OLD | NEW |