winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

ExecutionReporter.cpp (12760B)


      1 // Copyright (c) Microsoft Corporation.
      2 // Licensed under the MIT License.
      3 #include "pch.h"
      4 #include "ExecutionReporter.h"
      5 #include <AppInstallerErrors.h>
      6 
      7 
      8 namespace AppInstaller::CLI::Execution
      9 {
     10     using namespace Settings;
     11     using namespace VirtualTerminal;
     12 
     13     const Sequence& HelpCommandEmphasis = TextFormat::Foreground::Bright;
     14     const Sequence& HelpArgumentEmphasis = TextFormat::Foreground::Bright;
     15     const Sequence& ManifestInfoEmphasis = TextFormat::Foreground::Bright;
     16     const Sequence& SourceInfoEmphasis = TextFormat::Foreground::Bright;
     17     const Sequence& NameEmphasis = TextFormat::Foreground::BrightCyan;
     18     const Sequence& IdEmphasis = TextFormat::Foreground::BrightCyan;
     19     const Sequence& UrlEmphasis = TextFormat::Foreground::BrightBlue;
     20     const Sequence& PromptEmphasis = TextFormat::Foreground::Bright;
     21     const Sequence& ConvertToUpgradeFlowEmphasis = TextFormat::Foreground::BrightYellow;
     22     const Sequence& ConfigurationIntentEmphasis = TextFormat::Foreground::Bright;
     23     const Sequence& ConfigurationUnitEmphasis = TextFormat::Foreground::BrightCyan;
     24     const Sequence& AuthenticationEmphasis = TextFormat::Foreground::BrightYellow;
     25 
     26     namespace
     27     {
     28         DWORD GetStdHandleType(DWORD stdHandle)
     29         {
     30             DWORD result = FILE_TYPE_UNKNOWN;
     31 
     32             HANDLE handle = GetStdHandle(stdHandle);
     33             if (handle != INVALID_HANDLE_VALUE && handle != NULL)
     34             {
     35                 result = GetFileType(handle);
     36             }
     37 
     38             return result;
     39         }
     40     }
     41 
     42     Reporter::Reporter() :
     43         Reporter(std::cout, std::cin)
     44     {
     45         m_outStreamFileType = GetStdHandleType(STD_OUTPUT_HANDLE);
     46         m_inStreamFileType = GetStdHandleType(STD_INPUT_HANDLE);
     47     }
     48 
     49     Reporter::Reporter(std::ostream& outStream, std::istream& inStream) :
     50         Reporter(std::make_shared<BaseStream>(outStream, true, ConsoleModeRestore::Instance().IsVTEnabled()), inStream)
     51     {
     52         SetProgressSink(this);
     53     }
     54 
     55     Reporter::Reporter(std::shared_ptr<BaseStream> outStream, std::istream& inStream) :
     56         m_out(outStream),
     57         m_in(inStream)
     58     {
     59         auto sixelSupported = [&]() { return SixelsSupported(); };
     60         m_spinner = IIndefiniteSpinner::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), VisualStyle::Accent, sixelSupported);
     61         m_progressBar = IProgressBar::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), VisualStyle::Accent, sixelSupported);
     62 
     63         SetProgressSink(this);
     64     }
     65 
     66     Reporter::~Reporter()
     67     {
     68         this->CloseOutputStream();
     69     }
     70 
     71     Reporter::Reporter(const Reporter& other, clone_t) :
     72         Reporter(other.m_out, other.m_in)
     73     {
     74         m_outStreamFileType = other.m_outStreamFileType;
     75         m_inStreamFileType = other.m_inStreamFileType;
     76 
     77         SetChannel(other.m_channel);
     78 
     79         if (other.m_style.has_value())
     80         {
     81             SetStyle(*other.m_style);
     82         }
     83     }
     84 
     85     std::optional<PrimaryDeviceAttributes> Reporter::GetPrimaryDeviceAttributes()
     86     {
     87         if (ConsoleModeRestore::Instance().IsVTEnabled())
     88         {
     89             return PrimaryDeviceAttributes{ m_out->Get(), m_in };
     90         }
     91         else
     92         {
     93             return std::nullopt;
     94         }
     95     }
     96 
     97     OutputStream Reporter::GetOutputStream(Level level)
     98     {
     99         // If the level is not enabled, return a default stream which is disabled
    100         if (WI_AreAllFlagsClear(m_enabledLevels, level))
    101         {
    102             return OutputStream(*m_out, false, false);
    103         }
    104 
    105         OutputStream result = GetBasicOutputStream();
    106 
    107         switch (level)
    108         {
    109         case Level::Verbose:
    110             result.AddFormat(TextFormat::Default);
    111             break;
    112         case Level::Info:
    113             result.AddFormat(TextFormat::Default);
    114             break;
    115         case Level::Warning:
    116             result.AddFormat(TextFormat::Foreground::BrightYellow);
    117             break;
    118         case Level::Error:
    119             result.AddFormat(TextFormat::Foreground::BrightRed);
    120             break;
    121         default:
    122             THROW_HR(E_UNEXPECTED);
    123         }
    124 
    125         return result;
    126     }
    127 
    128     OutputStream Reporter::GetBasicOutputStream()
    129     {
    130         return { *m_out, m_channel == Channel::Output };
    131     }
    132 
    133     void Reporter::SetChannel(Channel channel)
    134     {
    135         m_channel = channel;
    136 
    137         if (m_channel != Channel::Output)
    138         {
    139             // Disable progress for non-output channels
    140             m_spinner.reset();
    141             m_progressBar.reset();
    142         }
    143     }
    144 
    145     void Reporter::SetStyle(VisualStyle style)
    146     {
    147         m_style = style;
    148 
    149         if (m_channel == Channel::Output)
    150         {
    151             auto sixelSupported = [&]() { return SixelsSupported(); };
    152             m_spinner = IIndefiniteSpinner::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), style, sixelSupported);
    153             m_progressBar = IProgressBar::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), style, sixelSupported);
    154         }
    155 
    156         if (style == VisualStyle::NoVT)
    157         {
    158             m_out->SetVTEnabled(false);
    159         }
    160     }
    161 
    162     std::istream& Reporter::RawInputStream()
    163     {
    164         return m_in;
    165     }
    166 
    167     bool Reporter::InputStreamIsInteractive() const
    168     {
    169         AICLI_LOG(CLI, Verbose, << "Reporter::m_inStreamFileType is " << m_inStreamFileType);
    170         return m_inStreamFileType == FILE_TYPE_CHAR;
    171     }
    172 
    173     bool Reporter::PromptForBoolResponse(Resource::LocString message, Level level, bool resultIfDisabled)
    174     {
    175         auto out = GetOutputStream(level);
    176 
    177         if (!out.IsEnabled())
    178         {
    179             return resultIfDisabled;
    180         }
    181 
    182         const std::vector<BoolPromptOption> options
    183         {
    184             BoolPromptOption{ Resource::String::PromptOptionYes, 'Y', true },
    185             BoolPromptOption{ Resource::String::PromptOptionNo, 'N', false },
    186         };
    187 
    188         out << message << std::endl;
    189 
    190         // Try prompting until we get a recognized option
    191         for (;;)
    192         {
    193             // Output all options
    194             for (size_t i = 0; i < options.size(); ++i)
    195             {
    196                 out << PromptEmphasis << "[" + options[i].Hotkey.get() + "] " + options[i].Label.get();
    197 
    198                 if (i + 1 == options.size())
    199                 {
    200                     out << PromptEmphasis << ": ";
    201                 }
    202                 else
    203                 {
    204                     out << "  ";
    205                 }
    206             }
    207 
    208             // Read the response
    209             std::string response;
    210             if (!std::getline(m_in, response))
    211             {
    212                 THROW_HR(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR);
    213             }
    214 
    215             // Find the matching option ignoring whitespace
    216             Utility::Trim(response);
    217             for (const auto& option : options)
    218             {
    219                 if (Utility::CaseInsensitiveEquals(response, option.Label) ||
    220                     Utility::CaseInsensitiveEquals(response, option.Hotkey))
    221                 {
    222                     return option.Value;
    223                 }
    224             }
    225         }
    226     }
    227 
    228     void Reporter::PromptForEnter(Level level)
    229     {
    230         auto out = GetOutputStream(level);
    231         if (!out.IsEnabled())
    232         {
    233             return;
    234         }
    235 
    236         out << std::endl << Resource::String::PressEnterToContinue << std::endl;
    237         m_in.get();
    238     }
    239 
    240     std::filesystem::path Reporter::PromptForPath(Resource::LocString message, Level level, std::filesystem::path resultIfDisabled)
    241     {
    242         auto out = GetOutputStream(level);
    243 
    244         if (!out.IsEnabled())
    245         {
    246             return resultIfDisabled;
    247         }
    248 
    249         // Try prompting until we get a valid answer
    250         for (;;)
    251         {
    252             out << message << ' ';
    253 
    254             // Read the response
    255             std::string response;
    256             if (!std::getline(m_in, response))
    257             {
    258                 THROW_HR(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR);
    259             }
    260 
    261             // Validate the path
    262             std::filesystem::path path{ response };
    263             if (path.is_absolute())
    264             {
    265                 return path;
    266             }
    267         }
    268 
    269     }
    270 
    271     void Reporter::ShowIndefiniteProgress(bool running)
    272     {
    273         if (m_spinner)
    274         {
    275             if (running)
    276             {
    277                 m_spinner->ShowSpinner();
    278             }
    279             else
    280             {
    281                 m_spinner->StopSpinner();
    282             }
    283         }
    284     }
    285 
    286     void Reporter::OnProgress(uint64_t current, uint64_t maximum, ProgressType type)
    287     {
    288         ShowIndefiniteProgress(false);
    289         if (m_progressBar)
    290         {
    291             m_progressBar->ShowProgress(current, maximum, type);
    292         }
    293     }
    294 
    295     void Reporter::SetProgressMessage(std::string_view message)
    296     {
    297         if (m_spinner)
    298         {
    299             m_spinner->SetMessage(message);
    300         }
    301     }
    302 
    303     void Reporter::BeginProgress()
    304     {
    305         GetBasicOutputStream() << VirtualTerminal::Cursor::Visibility::DisableShow;
    306         ShowIndefiniteProgress(true);
    307     };
    308 
    309     void Reporter::EndProgress(bool hideProgressWhenDone)
    310     {
    311         ShowIndefiniteProgress(false);
    312         if (m_progressBar)
    313         {
    314             m_progressBar->EndProgress(hideProgressWhenDone);
    315         }
    316         SetProgressMessage({});
    317         GetBasicOutputStream() << VirtualTerminal::Cursor::Visibility::EnableShow;
    318     };
    319 
    320     Reporter::AsyncProgressScope::AsyncProgressScope(Reporter& reporter, IProgressSink* sink, bool hideProgressWhenDone) :
    321         m_reporter(reporter), m_callback(sink)
    322     {
    323         reporter.SetProgressCallback(&m_callback);
    324         sink->BeginProgress();
    325         m_hideProgressWhenDone = hideProgressWhenDone;
    326     }
    327 
    328     Reporter::AsyncProgressScope::~AsyncProgressScope()
    329     {
    330         m_reporter.get().SetProgressCallback(nullptr);
    331         m_callback.GetSink()->EndProgress(m_hideProgressWhenDone);
    332     }
    333 
    334     ProgressCallback& Reporter::AsyncProgressScope::Callback()
    335     {
    336         return m_callback;
    337     }
    338 
    339     IProgressCallback* Reporter::AsyncProgressScope::operator->()
    340     {
    341         return &m_callback;
    342     }
    343 
    344     bool Reporter::AsyncProgressScope::HideProgressWhenDone() const
    345     {
    346         return m_hideProgressWhenDone;
    347     }
    348 
    349     void Reporter::AsyncProgressScope::HideProgressWhenDone(bool value)
    350     {
    351         m_hideProgressWhenDone.store(value);
    352     }
    353 
    354     std::unique_ptr<Reporter::AsyncProgressScope> Reporter::BeginAsyncProgress(bool hideProgressWhenDone)
    355     {
    356         return std::make_unique<AsyncProgressScope>(*this, m_progressSink.load(), hideProgressWhenDone);
    357     }
    358 
    359     void Reporter::SetProgressCallback(ProgressCallback* callback)
    360     {
    361         auto lock = m_progressCallbackLock.lock_exclusive();
    362         // Attempting two progress operations at the same time; not supported.
    363         THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_progressCallback != nullptr && callback != nullptr);
    364         m_progressCallback = callback;
    365     }
    366 
    367     void Reporter::CancelInProgressTask(bool force, CancelReason reason)
    368     {
    369         // TODO: Maybe ask the user if they really want to cancel?
    370         UNREFERENCED_PARAMETER(force);
    371         auto lock = m_progressCallbackLock.lock_shared();
    372         ProgressCallback* callback = m_progressCallback.load();
    373         if (callback)
    374         {
    375             if (!callback->IsCancelledBy(CancelReason::Any))
    376             {
    377                 callback->SetProgressMessage(Resource::String::CancellingOperation());
    378                 callback->Cancel(reason);
    379             }
    380         }
    381     }
    382 
    383     void Reporter::CloseOutputStream(bool forceDisable)
    384     {
    385         if (forceDisable)
    386         {
    387             m_out->Disable();
    388         }
    389         m_out->RestoreDefault();
    390     }
    391 
    392     void Reporter::SetLevelMask(Level reporterLevel, bool setEnabled) {
    393 
    394         if (setEnabled)
    395         {
    396             WI_SetAllFlags(m_enabledLevels, reporterLevel);
    397         }
    398         else
    399         {
    400             WI_ClearAllFlags(m_enabledLevels, reporterLevel);
    401         }
    402     }
    403 
    404     bool Reporter::SixelsSupported()
    405     {
    406         auto attributes = GetPrimaryDeviceAttributes();
    407         return (attributes ? attributes->Supports(PrimaryDeviceAttributes::Extension::Sixel) : false);
    408     }
    409 
    410     bool Reporter::SixelsEnabled()
    411     {
    412         return Settings::User().Get<Settings::Setting::EnableSixelDisplay>() && SixelsSupported();
    413     }
    414 }