blob: 95d6c2f31b324a705af7e6d763e4804607aa0015 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fidl/fuchsia.examples.calculator/cpp/fidl.h>
// Note: the pattern for the generated Natural bindings is:
// #include <fidl/<my.library.name>/cpp/fidl.h>
// For more information on the include path to the bindings, refer to:
// https://fuchsia.dev/fuchsia-src/development/languages/fidl/tutorials/cpp/basics/domain-objects#include-cpp-bindings
#include <lib/async-loop/cpp/loop.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <lib/syslog/cpp/log_settings.h>
#include <lib/syslog/cpp/macros.h>
#include <unistd.h>
#include <fstream>
#include <iostream>
#include <string>
// The parser library to handle an input file for the Calcutator
#include "examples/fidl/calculator/cpp/client/calc_parser.h"
// A Calculator Client class to help structure the code and make it more realistic. Could be easily
// extended to handle FIDL events by overriding fidl::AsyncEventHandler<>
class CalculatorClient {
public:
// Construct the Client with the loop dispatcher
explicit CalculatorClient(
async::Loop& loop, fidl::ClientEnd<fuchsia_examples_calculator::Calculator> client_endpoint)
: expected_responses_(0), loop_(loop) {
// We need to call Bind() since the fidl::Client is uninitialized
fidl_client_.Bind(std::move(client_endpoint), loop_.dispatcher());
// Check if the fidl::Client was initialized properly
if (!fidl_client_.is_valid()) {
FX_LOGS(ERROR) << "Client could not be bound!";
}
}
// A helper function to check for validity before making client calls
bool IsValid() { return fidl_client_.is_valid(); }
void Add(double left, double right) {
// Make the FIDL method call by dereferencing the |fidl_client| and registering the response
// callback.
// For more information about .ThenExactlyOnce(), please see:
// https://fuchsia.dev/fuchsia-src/development/languages/fidl/tutorials/cpp?#motivation_for_then
fidl_client_->Add({{.a = left, .b = right}})
.ThenExactlyOnce([&](fidl::Result<fuchsia_examples_calculator::Calculator::Add>& result) {
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Calculator client: " << __func__ << "(): "
<< "Failure receiving response: " << result.error_value();
}
FX_LOGS(INFO) << "Calculator client: " << __func__ << "(): "
<< " got response " << result->sum();
ReceivedResponseMaybeQuit();
});
}
void Subtract(double left, double right) {
// Make the FIDL method call by dereferencing the |fidl_client| and registering the response
// callback.
fidl_client_->Subtract({{.a = left, .b = right}})
.ThenExactlyOnce(
[&](fidl::Result<fuchsia_examples_calculator::Calculator::Subtract>& result) {
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Calculator client: " << __func__ << "(): "
<< "Failure receiving response: " << result.error_value();
}
FX_LOGS(INFO) << "Calculator client: " << __func__ << "(): "
<< " got response " << result->difference();
ReceivedResponseMaybeQuit();
});
}
void Multiply(double left, double right) {
// Make the FIDL method call by dereferencing the |fidl_client| and registering the response
// callback.
fidl_client_->Multiply({{.a = left, .b = right}})
.ThenExactlyOnce(
[&](fidl::Result<fuchsia_examples_calculator::Calculator::Multiply>& result) {
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Calculator client: " << __func__ << "(): "
<< "Failure receiving response: " << result.error_value();
}
FX_LOGS(INFO) << "Calculator client: " << __func__ << "(): "
<< " got response " << result->product();
ReceivedResponseMaybeQuit();
});
}
void Divide(double left, double right) {
// Make the FIDL method call by dereferencing the |fidl_client| and registering the response
// callback.
fidl_client_->Divide({{.dividend = left, .divisor = right}})
.ThenExactlyOnce(
[&](fidl::Result<fuchsia_examples_calculator::Calculator::Divide>& result) {
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Calculator client: " << __func__ << "(): "
<< "Failure receiving response: " << result.error_value();
}
FX_LOGS(INFO) << "Calculator client: " << __func__ << "(): "
<< " got response " << result->quotient();
ReceivedResponseMaybeQuit();
});
}
void Pow(double left, double right) {
// Make the FIDL method call by dereferencing the |fidl_client| and registering the response
// callback.
fidl_client_->Pow({{.base = left, .exponent = right}})
.ThenExactlyOnce([&](fidl::Result<fuchsia_examples_calculator::Calculator::Pow>& result) {
if (!result.is_ok()) {
FX_LOGS(ERROR) << "Calculator client: " << __func__ << "(): "
<< "Failure receiving response: " << result.error_value();
}
FX_LOGS(INFO) << "Calculator client: " << __func__ << "(): "
<< " got response " << result->power();
ReceivedResponseMaybeQuit();
});
}
void MakeClientRequest(calc::Expression& expression) {
// Increment the counter since we're about to make a request
expected_responses_++;
// Call the appropriate handler method
switch (expression.GetOperator()) {
case calc::Operator::Add:
Add(expression.GetLeft(), expression.GetRight());
break;
case calc::Operator::Subtract:
Subtract(expression.GetLeft(), expression.GetRight());
break;
case calc::Operator::Multiply:
Multiply(expression.GetLeft(), expression.GetRight());
break;
case calc::Operator::Divide:
Divide(expression.GetLeft(), expression.GetRight());
break;
case calc::Operator::Pow:
Pow(expression.GetLeft(), expression.GetRight());
break;
default:
FX_LOGS(ERROR) << "Not implemented on client";
// Since we didn't actually send a request, decrement the counter
expected_responses_--;
}
}
// A helper function so this async client shuts itself down after receiving all responses
void ReceivedResponseMaybeQuit() {
expected_responses_--;
if (expected_responses_ <= 0) {
FX_LOGS(INFO) << "Received all responses, shutting down client";
loop_.Quit();
}
}
private:
// A counter so we can shut down the async client loop when all responses have been received
size_t expected_responses_;
// Since this is an async client, it needs a loop dispatcher
async::Loop& loop_;
// The |fidl::Client| handle we use to make FIDL method calls
fidl::Client<fuchsia_examples_calculator::Calculator> fidl_client_;
};
int main(int argc, const char** argv) {
fuchsia_logging::SetTags({"calculator_client"});
// Note the path starts with /pkg/ even though the build rule
// `resource("input")` uses `data/input.txt`. At runtime, components are
// able to read the contents of their own package by accessing the path
// /pkg/ in their namespace. See
// https://fuchsia.dev/fuchsia-src/development/components/data#including_resources_with_a_component
// for more details.
const std::string input_filename = "/pkg/data/input.txt";
std::ifstream myfile(input_filename);
if (!myfile.is_open()) {
FX_LOGS(ERROR) << "Failed opening the input file: '" << input_filename << "'";
// TODO(https://fxbug.dev/42178809): Remove sleep once the logging bug is fixed.
sleep(1);
return 0;
}
// Parse file contents into memory
std::vector<std::string> file_contents;
while (myfile) {
std::string current_line;
std::getline(myfile, current_line);
file_contents.push_back(current_line);
}
// Connect to the fuchsia.examples.calculator/Calculator protocol. This can fail so it's wrapped
// in a |zx::result| and it must be checked for errors.
// Note: The actual success type type is |fidl::ClientEnd|, which is used to create the
// |fidl::Client|
zx::result client_end = component::Connect<fuchsia_examples_calculator::Calculator>();
if (!client_end.is_ok()) {
FX_LOGS(ERROR) << "Synchronous error when connecting to the |Calculator| protocol: "
<< client_end.status_string();
return -1;
}
// As in the server, the code sets up an async loop so that the client can
// listen for incoming responses from the server without blocking.
async::Loop loop(&kAsyncLoopConfigNeverAttachToThread);
// Create the Client instance
CalculatorClient client_instance(loop, std::move(*client_end));
// Check if the client initialized correctly
if (!client_instance.IsValid()) {
// TODO(https://fxbug.dev/42178809): Remove sleep once the logging bug is fixed.
sleep(1);
return -1;
}
// Send the parsed commands as FIDL requests to the Calculator server
for (auto current_cmd_line : file_contents) {
// Create the Expression
calc::Expression current_expression(current_cmd_line);
// Check to make sure we have a valid Expression
if (current_expression.GetOperator() == calc::Operator::PlaceHolderError) {
FX_LOGS(WARNING) << "Invalid input to calculator client, skipping: '" << current_cmd_line
<< "'";
// Skip this, as we don't have a valid Expression
continue;
}
// Call the appropriate client method
client_instance.MakeClientRequest(current_expression);
}
// Run() the async loop dispatcher to handle callbacks
loop.Run();
// TODO(https://fxbug.dev/42178809): Remove sleep once the logging bug is fixed.
sleep(1);
return 0;
}