mirror of
https://github.com/microsoft/vcpkg.git
synced 2025-06-06 09:13:28 +08:00
[vcpkg-contact-survey] Add monthly survey prompt
This commit is contained in:
parent
34d8c77d35
commit
71f8958a06
@ -41,6 +41,7 @@
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <stdexcept>
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <CppUnitTest.h>
|
||||
|
||||
#include <vcpkg/base/chrono.h>
|
||||
#include <vcpkg/base/sortedvector.h>
|
||||
#include <vcpkg/base/strings.h>
|
||||
#include <vcpkg/base/util.h>
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <time.h>
|
||||
#include <vcpkg/base/optional.h>
|
||||
|
||||
namespace vcpkg::Chrono
|
||||
{
|
||||
@ -44,4 +46,21 @@ namespace vcpkg::Chrono
|
||||
private:
|
||||
std::chrono::high_resolution_clock::time_point m_start_tick;
|
||||
};
|
||||
|
||||
class CTime
|
||||
{
|
||||
public:
|
||||
static Optional<CTime> get_current_date_time();
|
||||
static Optional<CTime> parse(CStringView str);
|
||||
|
||||
constexpr CTime() : m_tm{0} {}
|
||||
explicit constexpr CTime(tm t) : m_tm{t} {}
|
||||
|
||||
std::string to_string() const;
|
||||
|
||||
std::chrono::system_clock::time_point to_time_point() const;
|
||||
|
||||
private:
|
||||
mutable tm m_tm;
|
||||
};
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ namespace vcpkg
|
||||
struct GlobalState
|
||||
{
|
||||
static Util::LockGuarded<Chrono::ElapsedTimer> timer;
|
||||
static Util::LockGuarded<std::string> g_surveydate;
|
||||
|
||||
static std::atomic<bool> debugging;
|
||||
static std::atomic<bool> feature_packages;
|
||||
|
||||
|
20
toolsrc/include/vcpkg/userconfig.h
Normal file
20
toolsrc/include/vcpkg/userconfig.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vcpkg/base/files.h>
|
||||
|
||||
namespace vcpkg
|
||||
{
|
||||
struct UserConfig
|
||||
{
|
||||
std::string user_id;
|
||||
std::string user_time;
|
||||
std::string user_mac;
|
||||
|
||||
std::string last_completed_survey;
|
||||
|
||||
static UserConfig try_read_data(const Files::Filesystem& fs);
|
||||
|
||||
void try_write_data(Files::Filesystem& fs) const;
|
||||
};
|
||||
}
|
41
toolsrc/src/tests.chrono.cpp
Normal file
41
toolsrc/src/tests.chrono.cpp
Normal file
@ -0,0 +1,41 @@
|
||||
#include "tests.pch.h"
|
||||
|
||||
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
|
||||
|
||||
namespace Chrono = vcpkg::Chrono;
|
||||
|
||||
namespace UnitTest1
|
||||
{
|
||||
class ChronoTests : public TestClass<ChronoTests>
|
||||
{
|
||||
TEST_METHOD(parse_time)
|
||||
{
|
||||
auto timestring = "1990-02-03T04:05:06.0Z";
|
||||
auto maybe_time = Chrono::CTime::parse(timestring);
|
||||
|
||||
Assert::IsTrue(maybe_time.has_value());
|
||||
|
||||
Assert::AreEqual(timestring, maybe_time.get()->to_string().c_str());
|
||||
}
|
||||
|
||||
TEST_METHOD(parse_time_blank)
|
||||
{
|
||||
auto maybe_time = Chrono::CTime::parse("");
|
||||
|
||||
Assert::IsFalse(maybe_time.has_value());
|
||||
}
|
||||
|
||||
TEST_METHOD(time_difference)
|
||||
{
|
||||
auto maybe_time1 = Chrono::CTime::parse("1990-02-03T04:05:06.0Z");
|
||||
auto maybe_time2 = Chrono::CTime::parse("1990-02-10T04:05:06.0Z");
|
||||
|
||||
Assert::IsTrue(maybe_time1.has_value());
|
||||
Assert::IsTrue(maybe_time2.has_value());
|
||||
|
||||
auto delta = maybe_time2.get()->to_time_point() - maybe_time1.get()->to_time_point();
|
||||
|
||||
Assert::AreEqual(24 * 7, std::chrono::duration_cast<std::chrono::hours>(delta).count());
|
||||
}
|
||||
};
|
||||
}
|
@ -20,11 +20,13 @@
|
||||
#include <vcpkg/input.h>
|
||||
#include <vcpkg/metrics.h>
|
||||
#include <vcpkg/paragraphs.h>
|
||||
#include <vcpkg/userconfig.h>
|
||||
#include <vcpkg/vcpkglib.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
|
||||
#pragma comment(lib, "ole32")
|
||||
#pragma comment(lib, "shell32")
|
||||
@ -110,6 +112,28 @@ static void inner(const VcpkgCmdArguments& args)
|
||||
if (args.command != "autocomplete")
|
||||
{
|
||||
Commands::Version::warn_if_vcpkg_version_mismatch(paths);
|
||||
std::string surveydate = *GlobalState::g_surveydate.lock();
|
||||
auto maybe_surveydate = Chrono::CTime::parse(surveydate);
|
||||
if (auto p_surveydate = maybe_surveydate.get())
|
||||
{
|
||||
auto delta = std::chrono::system_clock::now() - p_surveydate->to_time_point();
|
||||
// 24 hours/day * 30 days/month
|
||||
if (std::chrono::duration_cast<std::chrono::hours>(delta).count() > 24 * 30)
|
||||
{
|
||||
std::default_random_engine generator(
|
||||
static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count()));
|
||||
std::uniform_int_distribution<int> distribution(1, 4);
|
||||
|
||||
if (distribution(generator) == 1)
|
||||
{
|
||||
Metrics::g_metrics.lock()->track_property("surveyprompt", "true");
|
||||
System::println(
|
||||
System::Color::success,
|
||||
"Your feedback is important to improve Vcpkg! Please take 3 minutes to complete our survey "
|
||||
"by running: vcpkg contact --survey");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto command_function = find_command(Commands::get_available_commands_type_b()))
|
||||
@ -148,80 +172,41 @@ static void inner(const VcpkgCmdArguments& args)
|
||||
static void load_config()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
fs::path localappdata;
|
||||
{
|
||||
// Config path in AppDataLocal
|
||||
wchar_t* localappdatapath = nullptr;
|
||||
if (S_OK != SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localappdatapath)) __fastfail(1);
|
||||
localappdata = localappdatapath;
|
||||
CoTaskMemFree(localappdatapath);
|
||||
}
|
||||
auto& fs = Files::get_real_filesystem();
|
||||
|
||||
std::string user_id, user_time, user_mac;
|
||||
try
|
||||
{
|
||||
auto maybe_pghs = Paragraphs::get_paragraphs(Files::get_real_filesystem(), localappdata / "vcpkg" / "config");
|
||||
if (const auto p_pghs = maybe_pghs.get())
|
||||
{
|
||||
const auto& pghs = *p_pghs;
|
||||
|
||||
std::unordered_map<std::string, std::string> keys;
|
||||
if (pghs.size() > 0) keys = pghs[0];
|
||||
|
||||
for (size_t x = 1; x < pghs.size(); ++x)
|
||||
{
|
||||
for (auto&& p : pghs[x])
|
||||
keys.insert(p);
|
||||
}
|
||||
|
||||
user_id = keys["User-Id"];
|
||||
user_time = keys["User-Since"];
|
||||
user_mac = keys["Mac-Hash"];
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
auto config = UserConfig::try_read_data(fs);
|
||||
|
||||
bool write_config = false;
|
||||
|
||||
// config file not found, could not be read, or invalid
|
||||
if (user_id.empty() || user_time.empty())
|
||||
if (config.user_id.empty() || config.user_time.empty())
|
||||
{
|
||||
::vcpkg::Metrics::Metrics::init_user_information(user_id, user_time);
|
||||
::vcpkg::Metrics::Metrics::init_user_information(config.user_id, config.user_time);
|
||||
write_config = true;
|
||||
}
|
||||
|
||||
if (user_mac.empty())
|
||||
if (config.user_mac.empty())
|
||||
{
|
||||
user_mac = Metrics::get_MAC_user();
|
||||
config.user_mac = Metrics::get_MAC_user();
|
||||
write_config = true;
|
||||
}
|
||||
|
||||
{
|
||||
auto locked_metrics = Metrics::g_metrics.lock();
|
||||
locked_metrics->set_user_information(user_id, user_time);
|
||||
locked_metrics->track_property("user_mac", user_mac);
|
||||
locked_metrics->set_user_information(config.user_id, config.user_time);
|
||||
locked_metrics->track_property("user_mac", config.user_mac);
|
||||
}
|
||||
|
||||
if (config.last_completed_survey.empty())
|
||||
{
|
||||
config.last_completed_survey = config.user_time;
|
||||
}
|
||||
|
||||
GlobalState::g_surveydate.lock()->assign(config.last_completed_survey);
|
||||
|
||||
if (write_config)
|
||||
{
|
||||
try
|
||||
{
|
||||
std::error_code ec;
|
||||
auto& fs = Files::get_real_filesystem();
|
||||
fs.create_directory(localappdata / "vcpkg", ec);
|
||||
fs.write_contents(localappdata / "vcpkg" / "config",
|
||||
Strings::format("User-Id: %s\n"
|
||||
"User-Since: %s\n"
|
||||
"Mac-Hash: %s\n",
|
||||
user_id,
|
||||
user_time,
|
||||
user_mac));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
config.try_write_data(fs);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -60,4 +60,66 @@ namespace vcpkg::Chrono
|
||||
std::string ElapsedTime::to_string() const { return format_time_userfriendly(as<std::chrono::nanoseconds>()); }
|
||||
|
||||
std::string ElapsedTimer::to_string() const { return elapsed().to_string(); }
|
||||
|
||||
Optional<CTime> CTime::get_current_date_time()
|
||||
{
|
||||
CTime ret;
|
||||
|
||||
#if defined(_WIN32)
|
||||
struct _timeb timebuffer;
|
||||
|
||||
_ftime_s(&timebuffer);
|
||||
|
||||
const errno_t err = gmtime_s(&ret.m_tm, &timebuffer.time);
|
||||
|
||||
if (err)
|
||||
{
|
||||
return nullopt;
|
||||
}
|
||||
#else
|
||||
time_t now = {0};
|
||||
time(&now);
|
||||
auto null_if_failed = gmtime_r(&now, &ret.m_tm);
|
||||
if (null_if_failed == nullptr)
|
||||
{
|
||||
return nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Optional<CTime> CTime::parse(CStringView str)
|
||||
{
|
||||
CTime ret;
|
||||
auto assigned = sscanf_s(str.c_str(),
|
||||
"%d-%d-%dT%d:%d:%d.",
|
||||
&ret.m_tm.tm_year,
|
||||
&ret.m_tm.tm_mon,
|
||||
&ret.m_tm.tm_mday,
|
||||
&ret.m_tm.tm_hour,
|
||||
&ret.m_tm.tm_min,
|
||||
&ret.m_tm.tm_sec);
|
||||
if (assigned != 6) return nullopt;
|
||||
if (ret.m_tm.tm_year < 1900) return nullopt;
|
||||
ret.m_tm.tm_year -= 1900;
|
||||
if (ret.m_tm.tm_mon < 1) return nullopt;
|
||||
ret.m_tm.tm_mon -= 1;
|
||||
mktime(&ret.m_tm);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string CTime::to_string() const
|
||||
{
|
||||
std::array<char, 80> date;
|
||||
date.fill(0);
|
||||
|
||||
strftime(&date[0], date.size(), "%Y-%m-%dT%H:%M:%S.0Z", &m_tm);
|
||||
return &date[0];
|
||||
}
|
||||
std::chrono::system_clock::time_point CTime::to_time_point() const
|
||||
{
|
||||
auto t = mktime(&m_tm);
|
||||
return std::chrono::system_clock::from_time_t(t);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <vcpkg/base/chrono.h>
|
||||
#include <vcpkg/base/system.h>
|
||||
#include <vcpkg/commands.h>
|
||||
#include <vcpkg/help.h>
|
||||
#include <vcpkg/userconfig.h>
|
||||
|
||||
namespace vcpkg::Commands::Contact
|
||||
{
|
||||
@ -28,6 +30,17 @@ namespace vcpkg::Commands::Contact
|
||||
|
||||
if (Util::Sets::contains(parsed_args.switches, switches[0].name))
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
auto maybe_now = Chrono::CTime::get_current_date_time();
|
||||
if (auto p_now = maybe_now.get())
|
||||
{
|
||||
auto& fs = Files::get_real_filesystem();
|
||||
auto config = UserConfig::try_read_data(fs);
|
||||
config.last_completed_survey = p_now->to_string();
|
||||
config.try_write_data(fs);
|
||||
}
|
||||
#endif
|
||||
|
||||
System::cmd_execute("start https://aka.ms/NPS_vcpkg");
|
||||
System::println("Default browser launched to https://aka.ms/NPS_vcpkg, thank you for your feedback!");
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
namespace vcpkg
|
||||
{
|
||||
Util::LockGuarded<Chrono::ElapsedTimer> GlobalState::timer;
|
||||
Util::LockGuarded<std::string> GlobalState::g_surveydate;
|
||||
|
||||
std::atomic<bool> GlobalState::debugging(false);
|
||||
std::atomic<bool> GlobalState::feature_packages(false);
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#include <vcpkg/metrics.h>
|
||||
|
||||
#include <vcpkg/base/chrono.h>
|
||||
#include <vcpkg/base/files.h>
|
||||
#include <vcpkg/base/strings.h>
|
||||
#include <vcpkg/base/system.h>
|
||||
@ -15,32 +16,13 @@ namespace vcpkg::Metrics
|
||||
|
||||
static std::string get_current_date_time()
|
||||
{
|
||||
struct tm newtime;
|
||||
std::array<char, 80> date;
|
||||
date.fill(0);
|
||||
|
||||
#if defined(_WIN32)
|
||||
struct _timeb timebuffer;
|
||||
|
||||
_ftime_s(&timebuffer);
|
||||
time_t now = timebuffer.time;
|
||||
const int milli = timebuffer.millitm;
|
||||
|
||||
const errno_t err = gmtime_s(&newtime, &now);
|
||||
|
||||
if (err)
|
||||
auto maybe_time = Chrono::CTime::get_current_date_time();
|
||||
if (auto ptime = maybe_time.get())
|
||||
{
|
||||
return "";
|
||||
return ptime->to_string();
|
||||
}
|
||||
#else
|
||||
time_t now;
|
||||
time(&now);
|
||||
gmtime_r(&now, &newtime);
|
||||
const int milli = 0;
|
||||
#endif
|
||||
|
||||
strftime(&date[0], date.size(), "%Y-%m-%dT%H:%M:%S", &newtime);
|
||||
return std::string(&date[0]) + "." + std::to_string(milli) + "Z";
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string generate_random_UUID()
|
||||
|
83
toolsrc/src/vcpkg/userconfig.cpp
Normal file
83
toolsrc/src/vcpkg/userconfig.cpp
Normal file
@ -0,0 +1,83 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <vcpkg/base/files.h>
|
||||
#include <vcpkg/base/lazy.h>
|
||||
#include <vcpkg/paragraphs.h>
|
||||
#include <vcpkg/userconfig.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
static vcpkg::Lazy<fs::path> s_localappdata;
|
||||
|
||||
static const fs::path& get_localappdata()
|
||||
{
|
||||
return s_localappdata.get_lazy([]() {
|
||||
fs::path localappdata;
|
||||
{
|
||||
// Config path in AppDataLocal
|
||||
wchar_t* localappdatapath = nullptr;
|
||||
if (S_OK != SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, nullptr, &localappdatapath)) __fastfail(1);
|
||||
localappdata = localappdatapath;
|
||||
CoTaskMemFree(localappdatapath);
|
||||
}
|
||||
return localappdata;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
namespace vcpkg
|
||||
{
|
||||
UserConfig UserConfig::try_read_data(const Files::Filesystem& fs)
|
||||
{
|
||||
UserConfig ret;
|
||||
|
||||
try
|
||||
{
|
||||
auto maybe_pghs = Paragraphs::get_paragraphs(fs, get_localappdata() / "vcpkg" / "config");
|
||||
if (const auto p_pghs = maybe_pghs.get())
|
||||
{
|
||||
const auto& pghs = *p_pghs;
|
||||
|
||||
std::unordered_map<std::string, std::string> keys;
|
||||
if (pghs.size() > 0) keys = pghs[0];
|
||||
|
||||
for (size_t x = 1; x < pghs.size(); ++x)
|
||||
{
|
||||
for (auto&& p : pghs[x])
|
||||
keys.insert(p);
|
||||
}
|
||||
|
||||
ret.user_id = keys["User-Id"];
|
||||
ret.user_time = keys["User-Since"];
|
||||
ret.user_mac = keys["Mac-Hash"];
|
||||
ret.last_completed_survey = keys["Survey-Completed"];
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void UserConfig::try_write_data(Files::Filesystem& fs) const
|
||||
{
|
||||
try
|
||||
{
|
||||
std::error_code ec;
|
||||
fs.create_directory(get_localappdata() / "vcpkg", ec);
|
||||
fs.write_contents(get_localappdata() / "vcpkg" / "config",
|
||||
Strings::format("User-Id: %s\n"
|
||||
"User-Since: %s\n"
|
||||
"Mac-Hash: %s\n"
|
||||
"Survey-Completed: %s\n",
|
||||
user_id,
|
||||
user_time,
|
||||
user_mac,
|
||||
last_completed_survey));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -178,6 +178,7 @@
|
||||
<ClInclude Include="..\include\vcpkg\statusparagraphs.h" />
|
||||
<ClInclude Include="..\include\vcpkg\triplet.h" />
|
||||
<ClInclude Include="..\include\vcpkg\update.h" />
|
||||
<ClInclude Include="..\include\vcpkg\userconfig.h" />
|
||||
<ClInclude Include="..\include\vcpkg\vcpkgcmdarguments.h" />
|
||||
<ClInclude Include="..\include\vcpkg\vcpkglib.h" />
|
||||
<ClInclude Include="..\include\vcpkg\vcpkgpaths.h" />
|
||||
@ -240,6 +241,7 @@
|
||||
<ClCompile Include="..\src\vcpkg\statusparagraphs.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\triplet.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\update.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\userconfig.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\vcpkgcmdarguments.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\vcpkglib.cpp" />
|
||||
<ClCompile Include="..\src\vcpkg\vcpkgpaths.cpp" />
|
||||
|
@ -192,6 +192,9 @@
|
||||
<ClCompile Include="..\src\vcpkg\base\system.cpp">
|
||||
<Filter>Source Files\vcpkg\base</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\vcpkg\userconfig.cpp">
|
||||
<Filter>Source Files\vcpkg</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\include\pch.h">
|
||||
@ -332,5 +335,8 @@
|
||||
<ClInclude Include="..\include\vcpkg\export.ifw.h">
|
||||
<Filter>Header Files\vcpkg</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\include\vcpkg\userconfig.h">
|
||||
<Filter>Header Files\vcpkg</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
@ -20,6 +20,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\src\tests.arguments.cpp" />
|
||||
<ClCompile Include="..\src\tests.chrono.cpp" />
|
||||
<ClCompile Include="..\src\tests.dependencies.cpp" />
|
||||
<ClCompile Include="..\src\tests.packagespec.cpp" />
|
||||
<ClCompile Include="..\src\tests.paragraph.cpp" />
|
||||
|
@ -42,6 +42,9 @@
|
||||
<ClCompile Include="..\src\tests.utils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\src\tests.chrono.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="..\include\tests.pch.h">
|
||||
|
Loading…
Reference in New Issue
Block a user