diff options
| author | Robert Schumacher <roschuma@microsoft.com> | 2017-12-01 16:08:09 -0800 |
|---|---|---|
| committer | Robert Schumacher <roschuma@microsoft.com> | 2017-12-01 16:08:09 -0800 |
| commit | 71f8958a069208b08a4bcca207956ef8a15238ab (patch) | |
| tree | 1fdded86553d97386fcd4d799bda4863fbe1516f | |
| parent | 34d8c77d35089484f66d80299dc6f8303a994a84 (diff) | |
| download | vcpkg-71f8958a069208b08a4bcca207956ef8a15238ab.tar.gz vcpkg-71f8958a069208b08a4bcca207956ef8a15238ab.zip | |
[vcpkg-contact-survey] Add monthly survey prompt
| -rw-r--r-- | toolsrc/include/pch.h | 1 | ||||
| -rw-r--r-- | toolsrc/include/tests.pch.h | 1 | ||||
| -rw-r--r-- | toolsrc/include/vcpkg/base/chrono.h | 19 | ||||
| -rw-r--r-- | toolsrc/include/vcpkg/globalstate.h | 2 | ||||
| -rw-r--r-- | toolsrc/include/vcpkg/userconfig.h | 20 | ||||
| -rw-r--r-- | toolsrc/src/tests.chrono.cpp | 41 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg.cpp | 95 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/base/chrono.cpp | 62 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/commands.contact.cpp | 13 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/globalstate.cpp | 2 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/metrics.cpp | 28 | ||||
| -rw-r--r-- | toolsrc/src/vcpkg/userconfig.cpp | 83 | ||||
| -rw-r--r-- | toolsrc/vcpkglib/vcpkglib.vcxproj | 2 | ||||
| -rw-r--r-- | toolsrc/vcpkglib/vcpkglib.vcxproj.filters | 6 | ||||
| -rw-r--r-- | toolsrc/vcpkgtest/vcpkgtest.vcxproj | 1 | ||||
| -rw-r--r-- | toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters | 3 |
16 files changed, 301 insertions, 78 deletions
diff --git a/toolsrc/include/pch.h b/toolsrc/include/pch.h index 5c31fbbd1..683bef171 100644 --- a/toolsrc/include/pch.h +++ b/toolsrc/include/pch.h @@ -41,6 +41,7 @@ #include <map> #include <memory> #include <mutex> +#include <random> #include <regex> #include <set> #include <stdexcept> diff --git a/toolsrc/include/tests.pch.h b/toolsrc/include/tests.pch.h index 0037af585..5c00fca4a 100644 --- a/toolsrc/include/tests.pch.h +++ b/toolsrc/include/tests.pch.h @@ -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> diff --git a/toolsrc/include/vcpkg/base/chrono.h b/toolsrc/include/vcpkg/base/chrono.h index c791f53fa..4291115f7 100644 --- a/toolsrc/include/vcpkg/base/chrono.h +++ b/toolsrc/include/vcpkg/base/chrono.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; + }; } diff --git a/toolsrc/include/vcpkg/globalstate.h b/toolsrc/include/vcpkg/globalstate.h index 40ec7958e..360d3f43e 100644 --- a/toolsrc/include/vcpkg/globalstate.h +++ b/toolsrc/include/vcpkg/globalstate.h @@ -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; diff --git a/toolsrc/include/vcpkg/userconfig.h b/toolsrc/include/vcpkg/userconfig.h new file mode 100644 index 000000000..63b8e5481 --- /dev/null +++ b/toolsrc/include/vcpkg/userconfig.h @@ -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; + }; +} diff --git a/toolsrc/src/tests.chrono.cpp b/toolsrc/src/tests.chrono.cpp new file mode 100644 index 000000000..269cdca58 --- /dev/null +++ b/toolsrc/src/tests.chrono.cpp @@ -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()); + } + }; +} diff --git a/toolsrc/src/vcpkg.cpp b/toolsrc/src/vcpkg.cpp index 04d44c414..094ea1dc5 100644 --- a/toolsrc/src/vcpkg.cpp +++ b/toolsrc/src/vcpkg.cpp @@ -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); - } - - 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; + auto& fs = Files::get_real_filesystem(); - 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 } diff --git a/toolsrc/src/vcpkg/base/chrono.cpp b/toolsrc/src/vcpkg/base/chrono.cpp index f0e450231..03c1ecce9 100644 --- a/toolsrc/src/vcpkg/base/chrono.cpp +++ b/toolsrc/src/vcpkg/base/chrono.cpp @@ -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); + } } diff --git a/toolsrc/src/vcpkg/commands.contact.cpp b/toolsrc/src/vcpkg/commands.contact.cpp index 07dcea80e..8063fe317 100644 --- a/toolsrc/src/vcpkg/commands.contact.cpp +++ b/toolsrc/src/vcpkg/commands.contact.cpp @@ -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!"); } diff --git a/toolsrc/src/vcpkg/globalstate.cpp b/toolsrc/src/vcpkg/globalstate.cpp index 149401b2c..123c77d46 100644 --- a/toolsrc/src/vcpkg/globalstate.cpp +++ b/toolsrc/src/vcpkg/globalstate.cpp @@ -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); diff --git a/toolsrc/src/vcpkg/metrics.cpp b/toolsrc/src/vcpkg/metrics.cpp index 69160705c..a0d40e7d3 100644 --- a/toolsrc/src/vcpkg/metrics.cpp +++ b/toolsrc/src/vcpkg/metrics.cpp @@ -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() diff --git a/toolsrc/src/vcpkg/userconfig.cpp b/toolsrc/src/vcpkg/userconfig.cpp new file mode 100644 index 000000000..d13a46f41 --- /dev/null +++ b/toolsrc/src/vcpkg/userconfig.cpp @@ -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 (...) + { + } + } +} diff --git a/toolsrc/vcpkglib/vcpkglib.vcxproj b/toolsrc/vcpkglib/vcpkglib.vcxproj index ae332e015..9a7ad6dc0 100644 --- a/toolsrc/vcpkglib/vcpkglib.vcxproj +++ b/toolsrc/vcpkglib/vcpkglib.vcxproj @@ -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" /> diff --git a/toolsrc/vcpkglib/vcpkglib.vcxproj.filters b/toolsrc/vcpkglib/vcpkglib.vcxproj.filters index e902bffbb..966fc7fb9 100644 --- a/toolsrc/vcpkglib/vcpkglib.vcxproj.filters +++ b/toolsrc/vcpkglib/vcpkglib.vcxproj.filters @@ -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>
\ No newline at end of file diff --git a/toolsrc/vcpkgtest/vcpkgtest.vcxproj b/toolsrc/vcpkgtest/vcpkgtest.vcxproj index 9eafc1ada..166216c45 100644 --- a/toolsrc/vcpkgtest/vcpkgtest.vcxproj +++ b/toolsrc/vcpkgtest/vcpkgtest.vcxproj @@ -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" /> diff --git a/toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters b/toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters index 2121f9782..422f9298e 100644 --- a/toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters +++ b/toolsrc/vcpkgtest/vcpkgtest.vcxproj.filters @@ -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"> |
