runner: periodically check if there's a new version available on github and offer a visit

This commit is contained in:
yuyoyuppe
2020-02-20 17:04:56 +03:00
committed by Andrey Nekrasov
parent c543b7585a
commit 0016836022
12 changed files with 224 additions and 3 deletions

View File

@@ -107,6 +107,7 @@
<ClInclude Include="dpi_aware.h" />
<ClInclude Include="com_object_factory.h" />
<ClInclude Include="notifications.h" />
<ClInclude Include="timeutil.h" />
<ClInclude Include="window_helpers.h" />
<ClInclude Include="icon_helpers.h" />
<ClInclude Include="json.h" />

View File

@@ -90,6 +90,9 @@
<ClInclude Include="com_object_factory.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="timeutil.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="d2d_svg.cpp">

View File

@@ -3,15 +3,21 @@
#include <msi.h>
#include <common/common.h>
#include <common/json.h>
#include <common/winstore.h>
#include <common/notifications.h>
#include <MsiQuery.h>
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Headers.h>
namespace
{
const wchar_t* POWER_TOYS_UPGRADE_CODE = L"{42B84BF7-5FBF-473B-9C8B-049DC16F7708}";
const wchar_t* DONT_SHOW_AGAIN_RECORD_REGISTRY_PATH = L"delete_previous_powertoys_confirm";
const wchar_t* USER_AGENT = L"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)";
const wchar_t* LATEST_RELEASE_ENDPOINT = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest";
}
namespace localized_strings
@@ -82,3 +88,32 @@ bool uninstall_msi_version(const std::wstring& package_path)
}
return false;
}
std::future<std::optional<new_version_download_info>> check_for_new_github_release_async()
{
try
{
winrt::Windows::Web::Http::HttpClient client;
auto headers = client.DefaultRequestHeaders();
headers.UserAgent().TryParseAdd(USER_AGENT);
auto response = co_await client.GetAsync(winrt::Windows::Foundation::Uri{ LATEST_RELEASE_ENDPOINT });
(void)response.EnsureSuccessStatusCode();
const auto body = co_await response.Content().ReadAsStringAsync();
auto json_body = json::JsonValue::Parse(body).GetObjectW();
auto new_version = json_body.GetNamedString(L"tag_name");
winrt::Windows::Foundation::Uri release_page_uri{ json_body.GetNamedString(L"html_url") };
const auto current_version = get_product_version();
if (new_version == current_version)
{
co_return std::nullopt;
}
co_return new_version_download_info{ std::move(release_page_uri), new_version.c_str() };
}
catch (...)
{
co_return std::nullopt;
}
}

View File

@@ -2,7 +2,17 @@
#include <optional>
#include <string>
#include <future>
#include <winrt/Windows.Foundation.h>
std::wstring get_msi_package_path();
bool uninstall_msi_version(const std::wstring& package_path);
bool offer_msi_uninstallation();
struct new_version_download_info
{
winrt::Windows::Foundation::Uri release_page_uri;
std::wstring version_string;
};
std::future<std::optional<new_version_download_info>> check_for_new_github_release_async();

View File

@@ -97,6 +97,7 @@
<LanguageStandard>stdcpplatest</LanguageStandard>
<TreatWarningAsError>true</TreatWarningAsError>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>
@@ -134,6 +135,7 @@
<LanguageStandard>stdcpplatest</LanguageStandard>
<TreatWarningAsError>true</TreatWarningAsError>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<SubSystem>Windows</SubSystem>

View File

@@ -7,6 +7,7 @@
namespace PTSettingsHelper {
std::wstring get_module_save_folder_location(std::wstring_view powertoy_name);
std::wstring get_root_save_folder_location();
void save_module_settings(std::wstring_view powertoy_name, json::JsonObject& settings);
json::JsonObject load_module_settings(std::wstring_view powertoy_name);

57
src/common/timeutil.h Normal file
View File

@@ -0,0 +1,57 @@
#pragma once
#include <ctime>
#include <cinttypes>
#include <string>
#include <optional>
#include <winrt/base.h>
namespace timeutil
{
inline std::wstring to_string(const time_t time)
{
return std::to_wstring(static_cast<uint64_t>(time));
}
inline std::optional<std::time_t> from_string(const std::wstring& s)
{
try
{
uint64_t i = std::stoull(s);
return static_cast<std::time_t>(i);
}
catch (...)
{
return std::nullopt;
}
}
inline std::time_t now()
{
return winrt::clock::to_time_t(winrt::clock::now());
}
namespace diff
{
inline int64_t in_seconds(const std::time_t to, const std::time_t from)
{
return static_cast<int64_t>(std::difftime(to, from));
}
inline int64_t in_minutes(const std::time_t to, const std::time_t from)
{
return static_cast<int64_t>(std::difftime(to, from) / 60);
}
inline int64_t in_hours(const std::time_t to, const std::time_t from)
{
return static_cast<int64_t>(std::difftime(to, from) / 3600);
}
inline int64_t in_days(const std::time_t to, const std::time_t from)
{
return static_cast<int64_t>(std::difftime(to, from) / (3600 * 24));
}
}
}

View File

@@ -16,6 +16,11 @@
#include <common/msi_to_msix_upgrade_lib/msi_to_msix_upgrade.h>
#include <common/winstore.h>
#include <common/notifications.h>
#include <common/timeutil.h>
#include "update_state.h"
#include <winrt/Windows.System.h>
#if _DEBUG && _WIN64
#include "unhandled_exception_handler.h"
@@ -26,6 +31,8 @@ extern "C" IMAGE_DOS_HEADER __ImageBase;
namespace localized_strings
{
const wchar_t MSI_VERSION_IS_ALREADY_RUNNING[] = L"An older version of PowerToys is already running.";
const wchar_t GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT[] = L"An update to PowerToys is available. Visit our GitHub page to get ";
const wchar_t GITHUB_NEW_VERSION_AGREE[] = L"Visit";
}
namespace
@@ -97,6 +104,46 @@ bool start_msi_uninstallation_sequence()
return exit_code == 0;
}
std::future<void> check_github_updates()
{
const auto new_version = co_await check_for_new_github_release_async();
if (!new_version)
{
co_return;
}
using namespace localized_strings;
std::wstring contents = GITHUB_NEW_VERSION_AVAILABLE_OFFER_VISIT;
contents += new_version->version_string;
contents += L'.';
notifications::show_toast_with_activations(contents, {}, { notifications::link_button{ GITHUB_NEW_VERSION_AGREE, new_version->release_page_uri.ToString() } });
}
void github_update_checking_worker()
{
const int64_t update_check_period_minutes = 60 * 24;
auto state = UpdateState::load();
for (;;)
{
int64_t sleep_minutes_till_next_update = 0;
if (state.github_update_last_checked_date.has_value())
{
int64_t last_checked_minutes_ago = timeutil::diff::in_minutes(timeutil::now(), *state.github_update_last_checked_date);
if (last_checked_minutes_ago < 0)
{
last_checked_minutes_ago = update_check_period_minutes;
}
sleep_minutes_till_next_update = max(0, update_check_period_minutes - last_checked_minutes_ago);
}
std::this_thread::sleep_for(std::chrono::minutes(sleep_minutes_till_next_update));
check_github_updates().get();
state.github_update_last_checked_date.emplace(timeutil::now());
state.save();
}
}
void alert_already_running()
{
MessageBoxW(nullptr,
@@ -271,12 +318,17 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
try
{
std::thread{ [] {
github_update_checking_worker();
} }.detach();
if (winstore::running_as_packaged())
{
std::thread{ [] {
start_msi_uninstallation_sequence();
} }.detach();
}
// Singletons initialization order needs to be preserved, first events and
// then modules to guarantee the reverse destruction order.
SystemMenuHelperInstace();

View File

@@ -64,6 +64,7 @@
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>..\common\inc;..\common\Telemetry;..;..\modules;..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<UACExecutionLevel>AsInvoker</UACExecutionLevel>
@@ -88,6 +89,7 @@
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>..\common\inc;..\common\Telemetry;..;..\modules;..\..\deps\cpprestsdk\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalOptions>/await %(AdditionalOptions)</AdditionalOptions>
</ClCompile>
<Link>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
@@ -117,6 +119,7 @@
<ClCompile Include="trace.cpp" />
<ClCompile Include="tray_icon.cpp" />
<ClCompile Include="unhandled_exception_handler.cpp" />
<ClCompile Include="update_state.cpp" />
<ClCompile Include="win_hook_event.cpp" />
</ItemGroup>
<ItemGroup>
@@ -124,6 +127,7 @@
<ClInclude Include="general_settings.h" />
<ClInclude Include="lowlevel_keyboard_event.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="update_state.h" />
<ClInclude Include="powertoys_events.h" />
<ClInclude Include="powertoy_module.h" />
<ClInclude Include="resource.h" />

View File

@@ -39,6 +39,9 @@
<ClCompile Include="restart_elevated.cpp">
<Filter>Utils</Filter>
</ClCompile>
<ClCompile Include="update_state.cpp">
<Filter>Utils</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -79,6 +82,9 @@
<ClInclude Include="restart_elevated.h">
<Filter>Utils</Filter>
</ClInclude>
<ClInclude Include="update_state.h">
<Filter>Utils</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="Utils">

View File

@@ -0,0 +1,38 @@
#include "pch.h"
#include "update_state.h"
#include <common/json.h>
#include <common/timeutil.h>
#include <common/settings_helpers.h>
namespace
{
const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\update_state.json";
}
UpdateState UpdateState::load()
{
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
auto json = json::from_file(file_name);
UpdateState state;
if (!json)
{
return state;
}
state.github_update_last_checked_date = timeutil::from_string(json->GetNamedString(L"github_update_last_checked_date", L"invalid").c_str());
return state;
}
void UpdateState::save()
{
json::JsonObject json;
if (github_update_last_checked_date.has_value())
{
json.SetNamedValue(L"github_update_last_checked_date", json::value(timeutil::to_string(*github_update_last_checked_date)));
}
const auto file_name = PTSettingsHelper::get_root_save_folder_location() + PERSISTENT_STATE_FILENAME;
json::to_file(file_name, json);
}

12
src/runner/update_state.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <ctime>
#include <optional>
struct UpdateState
{
std::optional<std::time_t> github_update_last_checked_date;
static UpdateState load();
void save();
};