blob: e4b8a734649b90d2664bac5270139047df572b22 [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.
#ifndef SRC_DEVELOPER_DEBUG_ZXDB_CONSOLE_COMMAND_CONTEXT_H_
#define SRC_DEVELOPER_DEBUG_ZXDB_CONSOLE_COMMAND_CONTEXT_H_
#include <lib/fit/defer.h>
#include <lib/fit/function.h>
#include <map>
#include "src/developer/debug/zxdb/client/analytics_event.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/console/async_output_buffer.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/lib/fxl/memory/ref_counted.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace zxdb {
class Command;
class Console;
class ConsoleContext;
// This object collects the output and errors from a command and tracks its completion.
//
// The command implementation must keep this object alive for as long as the command executes (which
// could be asynchronously). When the CommandContext is destroyed, the callbacks will executed and
// the command will be considered complete.
class CommandContext : public fxl::RefCountedThreadSafe<CommandContext> {
public:
// Writes the given buffer to the output.
virtual void Output(const OutputBuffer& output) = 0;
// Convenience wrapper to output the given string.
void Output(const std::string& s) { Output(OutputBuffer(s)); }
// Synchronously prints the output if the async buffer is complete. Otherwise adds a listener and
// prints the output to the console when it is complete.
//
// This call takes a reference to the CommandContext (keeping the command in a non-completed
// state) for as long as the AsyncOutputBuffer remains incomplete.
void Output(fxl::RefPtr<AsyncOutputBuffer> output);
// Reports that the command failed with the given error. The error will be printed to the screen.
virtual void ReportError(const Err& err) = 0;
// The Console/ConsoleContext may be null if this object has outlived the Console object. In
// production this probably won't happen but can be triggered in tests more easily.
//
// If the code calling this function is being used in a synchronous context (i.e. called directly
// from a command handler and not from a callback), these pointers are guaranteed non-null.
Console* console() { return weak_console_.get(); }
ConsoleContext* GetConsoleContext() const;
// Returns true if this command context has encountered any error.
bool has_error() const;
// Sets the completion observer used by the console to tell when the command is done. This is used
// for enabling and disabling input. The callback passed into the constructors of some derived
// classes are instead for the creator of the CommandContext (which may not necessarily be the
// console).
//
// Since this is currently used only for console integration, it's called the "Console" completion
// observer and there can be only one of them. If we have a need for more than one, we can
// generalize this in the future.
void SetConsoleCompletionObserver(fit::deferred_callback observer);
// Sets the command report for this command. This is called after a command has been successfully
// parsed. If the command fails parsing, we use the default constructed |report_| with the error
// given to |SetError| by the derived class.
void SetCommandReport(CommandReport other);
const CommandReport& GetCommandReport() const { return report_; }
protected:
FRIEND_REF_COUNTED_THREAD_SAFE(CommandContext);
// Console may be null.
explicit CommandContext(Console* console);
virtual ~CommandContext();
// Used by derived classes to set the offending error.
void SetError(const Err& err);
private:
fxl::WeakPtr<Console> weak_console_;
// Track all asynchronous output pending. We want to store a reference and lookup by pointer, so
// the object is duplicated here (RefPtr doesn't like to be put in a set).
//
// These pointers own the tree of async outputs for each async operation. We need to keep owning
// pointers to the roots of every AsyncOutputBuffer we've installed ourselves as a completion
// callback for to keep them in scope until they're completed.
std::map<AsyncOutputBuffer*, fxl::RefPtr<AsyncOutputBuffer>> async_output_;
// A report with all of the basic information for the command associated with this context. If
// analytics are enabled, this will contain all of the information sent for any particular
// command. This value is always valid. A default constructed report is made at construction time,
// which can be used to report parsing errors for something that was typed on the command line.
// Once a command has been parsed and filled in an instance of a Command, the default report is
// replaced with one created from that Command instance.
CommandReport report_;
fit::deferred_callback console_completion_observer_;
};
// This is the normal implementation that just outputs everything to the console.
class ConsoleCommandContext : public CommandContext {
public:
// A completion callback is issued when this object goes out of scope. It is passed the first
// error that was output (if any) which allows the caller to determine success or failure of the
// operation.
//
// This error (along with any subsequent ones) will have already been printed so does not need
// further processing in the common case.
using CompletionCallback = fit::callback<void(const Err& first_error)>;
using CommandContext::Output;
void Output(const OutputBuffer& output) override;
void ReportError(const Err& err) override;
private:
FRIEND_REF_COUNTED_THREAD_SAFE(ConsoleCommandContext);
FRIEND_MAKE_REF_COUNTED(ConsoleCommandContext);
// Console may be null.
explicit ConsoleCommandContext(Console* console, CompletionCallback done = CompletionCallback());
~ConsoleCommandContext() override;
CompletionCallback done_; // Possibly null.
Err first_error_;
};
// This command context implementation collects all the output
class OfflineCommandContext : public CommandContext {
public:
// A completion callback is issued when this object goes out of scope. It is passed all output
// buffers and errors that have been generated.
using CompletionCallback = fit::callback<void(OutputBuffer output, std::vector<Err> errors)>;
using CommandContext::Output;
void Output(const OutputBuffer& output) override;
void ReportError(const Err& err) override;
private:
FRIEND_REF_COUNTED_THREAD_SAFE(OfflineCommandContext);
FRIEND_MAKE_REF_COUNTED(OfflineCommandContext);
// Console may be null.
explicit OfflineCommandContext(Console* console, CompletionCallback done = CompletionCallback());
~OfflineCommandContext() override;
CompletionCallback done_; // Possibly null.
OutputBuffer output_;
std::vector<Err> errors_;
};
// This command context forwards everything to an underlying command context. It allows multiple
// commands to be sequenced (since each NestedCommandContext represents one step) while gathering
// the output into one place.
class NestedCommandContext : public CommandContext {
public:
// This completion callback represents just the error from this step.
using CompletionCallback = fit::callback<void(const Err& first_error)>;
using CommandContext::Output;
void Output(const OutputBuffer& output) override;
void ReportError(const Err& err) override;
private:
FRIEND_REF_COUNTED_THREAD_SAFE(NestedCommandContext);
FRIEND_MAKE_REF_COUNTED(NestedCommandContext);
explicit NestedCommandContext(fxl::RefPtr<CommandContext> parent,
CompletionCallback cb = CompletionCallback());
~NestedCommandContext() override;
fxl::RefPtr<CommandContext> parent_;
CompletionCallback done_; // Possibly null.
Err first_error_;
};
} // namespace zxdb
#endif // SRC_DEVELOPER_DEBUG_ZXDB_CONSOLE_COMMAND_CONTEXT_H_