#include "ash/scanner/scanner_action_handler.h"
#include <cstddef>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <variant>
#include "ash/public/cpp/new_window_delegate.h"
#include "ash/scanner/scanner_command.h"
#include "ash/scanner/scanner_command_delegate.h"
#include "base/check.h"
#include "base/containers/span.h"
#include "base/containers/to_vector.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_file.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/escape.h"
#include "base/strings/strcat.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/types/expected.h"
#include "components/drive/drive_api_util.h"
#include "components/drive/service/drive_api_service.h"
#include "components/drive/service/drive_service_interface.h"
#include "components/manta/proto/scanner.pb.h"
#include "google_apis/common/api_error_codes.h"
#include "google_apis/common/request_sender.h"
#include "google_apis/drive/drive_api_parser.h"
#include "google_apis/people/people_api_request_types.h"
#include "google_apis/people/people_api_requests.h"
#include "google_apis/people/people_api_response_types.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/abseil-cpp/absl/functional/overload.h"
#include "ui/base/clipboard/clipboard_data.h"
#include "url/gurl.h"
namespace ash {
namespace {
constexpr std::string_view kPersonResourceNamePrefix = "people/";
constexpr std::string_view kPersonContactsWebUiPath = "/person/";
const GURL& GetCalendarEventTemplateUrl() {
static GURL kGoogleCalendarEventTemplateUrl(
"https://calendar.google.com/calendar/render?action=TEMPLATE");
return kGoogleCalendarEventTemplateUrl;
}
const GURL& GetGoogleContactsBaseUrl() {
static GURL kGoogleContactsBaseUrl("https://contacts.google.com/");
return kGoogleContactsBaseUrl;
}
GURL GetCalendarEventUrl(const manta::proto::NewEventAction& event) {
std::string query = GetCalendarEventTemplateUrl().GetQuery();
CHECK(!query.empty());
if (!event.title().empty()) {
query += "&text=";
query += base::EscapeQueryParamValue(event.title(), true);
}
if (!event.description().empty()) {
query += "&details=";
query +=
base::EscapeQueryParamValue(event.description(), true);
}
if (!event.dates().empty()) {
query += "&dates=";
query += base::EscapeQueryParamValue(event.dates(), true);
}
if (!event.location().empty()) {
query += "&location=";
query += base::EscapeQueryParamValue(event.location(), true);
}
GURL::Replacements replacements;
replacements.SetQueryStr(query);
return GetCalendarEventTemplateUrl().ReplaceComponents(replacements);
}
GURL GetEditContactUrl(std::string_view resource_name) {
if (!resource_name.starts_with(kPersonResourceNamePrefix)) {
return GURL();
}
resource_name.remove_prefix(kPersonResourceNamePrefix.size());
GURL::Replacements replacements;
std::string path = base::StrCat({kPersonContactsWebUiPath, resource_name});
replacements.SetPathStr(path);
replacements.SetQueryStr("edit=1");
GURL edit_contact_url =
GetGoogleContactsBaseUrl().ReplaceComponents(replacements);
if (!edit_contact_url.path().starts_with(kPersonContactsWebUiPath)) {
return GURL();
}
return edit_contact_url;
}
void OpenInBrowserTab(base::WeakPtr<ScannerCommandDelegate> delegate,
const GURL& gurl,
ScannerCommandCallback callback) {
if (delegate == nullptr) {
std::move(callback).Run(false);
return;
}
delegate->OpenUrl(gurl);
std::move(callback).Run(true);
}
std::optional<base::ScopedTempFile> CreateTempFileWithContents(
std::string_view data) {
base::ScopedTempFile temp_file;
if (!temp_file.Create()) {
return std::nullopt;
}
base::File file(temp_file.path(),
base::File::FLAG_OPEN | base::File::FLAG_WRITE);
if (!file.IsValid()) {
return std::nullopt;
}
if (!file.WriteAndCheck(0, base::as_byte_span(data))) {
return std::nullopt;
}
return temp_file;
}
void OnTempFileUploaded(base::WeakPtr<ScannerCommandDelegate> delegate,
ScannerCommandCallback callback,
base::ScopedTempFile temp_file,
google_apis::ApiErrorCode error,
std::unique_ptr<google_apis::FileResource> entry) {
absl::Cleanup temp_file_cleanup = [temp_file =
std::move(temp_file)]() mutable {
base::ThreadPool::PostTask(
FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
base::DoNothingWithBoundArgs(std::move(temp_file)));
};
if (error != google_apis::ApiErrorCode::HTTP_SUCCESS &&
error != google_apis::ApiErrorCode::HTTP_CREATED) {
std::move(callback).Run(false);
return;
}
if (entry == nullptr) {
std::move(callback).Run(false);
return;
}
OpenInBrowserTab(std::move(delegate), entry->alternate_link(),
std::move(callback));
}
void UploadTempFileToDrive(base::WeakPtr<ScannerCommandDelegate> delegate,
std::string contents_mime_type,
std::string_view converted_mime_type,
size_t contents_size,
std::string title,
ScannerCommandCallback callback,
std::optional<base::ScopedTempFile> temp_file) {
if (!temp_file.has_value()) {
std::move(callback).Run(false);
return;
}
if (delegate == nullptr) {
std::move(callback).Run(false);
return;
}
drive::DriveServiceInterface* drive_service = delegate->GetDriveService();
if (drive_service == nullptr) {
std::move(callback).Run(false);
return;
}
base::FilePath temp_path = temp_file->path();
drive_service->MultipartUploadNewFile(
contents_mime_type, converted_mime_type, contents_size,
drive_service->GetRootResourceId(), title, temp_path,
drive::UploadNewFileOptions(),
base::BindOnce(&OnTempFileUploaded, std::move(delegate),
std::move(callback), std::move(*temp_file)),
base::NullCallback());
}
void HandleDriveUploadCommand(base::WeakPtr<ScannerCommandDelegate> delegate,
DriveUploadCommand command,
ScannerCommandCallback callback) {
size_t contents_size = command.contents.size();
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::TaskPriority::USER_BLOCKING, base::MayBlock()},
base::BindOnce(&CreateTempFileWithContents, std::move(command.contents)),
base::BindOnce(&UploadTempFileToDrive, std::move(delegate),
std::move(command.contents_mime_type),
std::move(command.converted_mime_type), contents_size,
std::move(command.title), std::move(callback)));
}
std::unique_ptr<ui::ClipboardData> ClipboardDataFromAction(
manta::proto::CopyToClipboardAction action) {
auto data = std::make_unique<ui::ClipboardData>();
if (!action.plain_text().empty()) {
data->set_text(std::move(*action.mutable_plain_text()));
}
if (!action.html_text().empty()) {
data->set_markup_data(std::move(*action.mutable_html_text()));
}
return data;
}
google_apis::people::Contact ContactFromAction(
manta::proto::NewContactAction action) {
google_apis::people::Contact contact;
contact.name.family_name = std::move(*action.mutable_family_name());
contact.name.given_name = std::move(*action.mutable_given_name());
if (action.email_addresses_size() > 0) {
contact.email_addresses = base::ToVector(
*action.mutable_email_addresses(),
[](manta::proto::NewContactAction::EmailAddress& proto_email) {
google_apis::people::EmailAddress email_address;
email_address.value = std::move(*proto_email.mutable_value());
email_address.type = std::move(*proto_email.mutable_type());
return email_address;
});
} else if (!action.email().empty()) {
google_apis::people::EmailAddress email_address;
email_address.value = std::move(*action.mutable_email());
contact.email_addresses.push_back(std::move(email_address));
}
if (action.phone_numbers_size() > 0) {
contact.phone_numbers = base::ToVector(
*action.mutable_phone_numbers(),
[](manta::proto::NewContactAction::PhoneNumber& proto_phone) {
google_apis::people::PhoneNumber phone_number;
phone_number.value = std::move(*proto_phone.mutable_value());
phone_number.type = std::move(*proto_phone.mutable_type());
return phone_number;
});
} else if (!action.phone().empty()) {
google_apis::people::PhoneNumber phone_number;
phone_number.value = std::move(*action.mutable_phone());
contact.phone_numbers.push_back(std::move(phone_number));
}
return contact;
}
void OnContactCreated(base::WeakPtr<ScannerCommandDelegate> delegate,
ScannerCommandCallback callback,
base::expected<google_apis::people::Person,
google_apis::ApiErrorCode> result) {
if (!result.has_value()) {
std::move(callback).Run(false);
return;
}
GURL edit_contact_url = GetEditContactUrl(result->resource_name);
if (!edit_contact_url.is_valid()) {
std::move(callback).Run(false);
return;
}
OpenInBrowserTab(std::move(delegate), edit_contact_url, std::move(callback));
}
}
ScannerCommand ScannerActionToCommand(manta::proto::ScannerAction action) {
switch (action.action_case()) {
case manta::proto::ScannerAction::kNewEvent:
return OpenUrlCommand(
GetCalendarEventUrl(std::move(*action.mutable_new_event())));
case manta::proto::ScannerAction::kNewContact:
return CreateContactCommand(
ContactFromAction(std::move(*action.mutable_new_contact())));
case manta::proto::ScannerAction::kNewGoogleDoc:
return DriveUploadCommand(
std::move(*action.mutable_new_google_doc()->mutable_title()),
std::move(*action.mutable_new_google_doc()->mutable_html_contents()),
"text/html",
drive::util::kGoogleDocumentMimeType);
case manta::proto::ScannerAction::kNewGoogleSheet:
return DriveUploadCommand(
std::move(*action.mutable_new_google_sheet()->mutable_title()),
std::move(*action.mutable_new_google_sheet()->mutable_csv_contents()),
"text/csv",
drive::util::kGoogleSpreadsheetMimeType);
case manta::proto::ScannerAction::kCopyToClipboard:
return CopyToClipboardCommand(ClipboardDataFromAction(
std::move(*action.mutable_copy_to_clipboard())));
case manta::proto::ScannerAction::ACTION_NOT_SET:
NOTREACHED();
}
}
void HandleScannerCommand(base::WeakPtr<ScannerCommandDelegate> delegate,
ScannerCommand command,
ScannerCommandCallback callback) {
if (delegate == nullptr) {
std::move(callback).Run(false);
return;
}
std::visit(
absl::Overload{
[&](OpenUrlCommand& command) {
OpenInBrowserTab(std::move(delegate), command.url,
std::move(callback));
},
[&](DriveUploadCommand& command) {
HandleDriveUploadCommand(std::move(delegate), std::move(command),
std::move(callback));
},
[&](CopyToClipboardCommand& command) {
delegate->SetClipboard(std::move(command.clipboard_data));
std::move(callback).Run(true);
},
[&](CreateContactCommand& command) {
google_apis::RequestSender* request_sender =
delegate->GetGoogleApisRequestSender();
if (request_sender == nullptr) {
std::move(callback).Run(false);
return;
}
request_sender->StartRequestWithAuthRetry(
std::make_unique<google_apis::people::CreateContactRequest>(
request_sender, std::move(command.contact),
base::BindOnce(&OnContactCreated, std::move(delegate),
std::move(callback))));
},
},
command);
}
}