Introduce Register API to take in assets#2447
Conversation
f429687 to
7a0a03a
Compare
|
|
| int main() | ||
| { | ||
| auto manager = winrt::AppNotificationManager::Default(); | ||
| manager.Register(L"AppNotifications", winrt::Windows::Foundation::Uri {L"<Path to Icon>"}); |
There was a problem hiding this comment.
I think it's okay to set a mock path (C:\Users\<user>\AppData\MyApp\icon.png or the app folder which may be under ProgramFiles?).
There was a problem hiding this comment.
Would like to see what everyone thinks of this before updating. Thanks.
There was a problem hiding this comment.
I like more specific over too general, as long as there aren't too many options and that we list the most common options. Maybe we use :\Users<user>\AppData\MyApp\icon.png as proposed by Daniel and we also put a comment with an example path to a folder inside the program file folder.
Maybe we should show the functions to call in order to retrieve the path(s) to the folders where the app is installed.
There was a problem hiding this comment.
Something like:
std::filesystem::path exePath{ GetPathToCurrentExe() };
auto iconPath{ exePath / "displayicon.png" };There was a problem hiding this comment.
Sample should show use of MRT or another resource loader indirection to fetch that display string, too.
|
|
||
| ## Registering for App Notifications using assets | ||
|
|
||
| For Unpackaged applications, the developer can opt to call into Register API with DisplayName and Icon. |
There was a problem hiding this comment.
"can Register using a custom DisplayName and Icon"?
| } | ||
|
|
||
| void AppNotificationManager::Register() | ||
| void AppNotificationManager::RegisterHelper(hstring const& displayName, std::wstring const& iconFilePath) |
There was a problem hiding this comment.
Why is one argument hstring and the other wstring? We should be consistent
| { | ||
| THROW_HR_IF_MSG(E_ILLEGAL_METHOD_CALL, AppModel::Identity::IsPackagedProcess(), "Not applicable for packaged applications"); | ||
|
|
||
| THROW_HR_IF(E_INVALIDARG, (displayName == winrt::hstring{}) || (iconUri == nullptr)); |
There was a problem hiding this comment.
Same issue with iconUri. If it is null, don't check. Let it crash further along. You should instead check for the validity of the uri's content: eg. empty path, invalid path etc.
There was a problem hiding this comment.
Atleast make sure that the uri is a valid file path and the file exists on disk.
| THROW_HR_IF(E_INVALIDARG, (displayName == winrt::hstring{}) || (iconUri == nullptr)); | ||
|
|
||
| std::wstring iconFilePath{ iconUri.Path() }; | ||
| iconFilePath = iconFilePath.substr(1, iconFilePath.size() - 1); |
There was a problem hiding this comment.
what is this substring for? Can you please explain this?
| std::wstring iconFilePath{ iconUri.Path() }; | ||
| iconFilePath = iconFilePath.substr(1, iconFilePath.size() - 1); | ||
| // Uri converts backward slashes to forward slashes | ||
| std::replace(iconFilePath.begin(), iconFilePath.end(), '/', '\\'); |
There was a problem hiding this comment.
You are actually converting forward slash to back slash. So the comment is incorrect. https://newbedev.com/difference-between-forward-slash-and-backslash-in-file-path
| std::wstring iconFilePath{ iconUri.Path() }; | ||
| iconFilePath = iconFilePath.substr(1, iconFilePath.size() - 1); | ||
| // Uri converts backward slashes to forward slashes | ||
| std::replace(iconFilePath.begin(), iconFilePath.end(), '/', '\\'); |
There was a problem hiding this comment.
This conversion is extremely fragile. We should not have to do it. Have you checked why we were erroring out with forward slash in the path? We should confirm why we see this behavior before making this change.
|
|
||
| void AppNotificationManager::Register() | ||
| { | ||
| RegisterHelper(winrt::hstring{} /* displayName */, std::wstring{} /* iconFilePath */); |
There was a problem hiding this comment.
Why don't you retrieve the correct displayname and icon path here before calling into the helper? Passing in empty is a strange pattern.
There was a problem hiding this comment.
Also Register would fail now because you call empty. Please don't use anti patterns and use valid arguments to pass into
APIs
| else | ||
| { | ||
| registeredClsid = RegisterComActivatorGuidAndAssets(); | ||
| registeredClsid = RegisterComActivatorGuidAndAssets(winrt::hstring{} /* displayName */, std::wstring{} /* iconUri */); |
There was a problem hiding this comment.
This is incorrect too. Don't pass empty arguments please. Instead pass in the overloaded arguments or the one from shortcut/process
There was a problem hiding this comment.
Actually why do we need to re-register assets at all? Aren't we simply re-routing to another process? @pmpurifoy ? Can you explain this one please and why we need to re-register assets as part of this flow?
There was a problem hiding this comment.
Actually what really needs to happen here is we need to fetch the registered Guid and nothing more. But I will wait for Paul to chip in here :)
std::wstring registeredGuid;
auto hr = GetActivatorGuid(registeredGuid);
There was a problem hiding this comment.
Call GetActivatorGuid
| { | ||
| winrt::AppNotificationManager::Default().Register(L"AppNotificationApp", nullptr); | ||
|
|
||
| winrt::AppNotificationManager::Default().UnregisterAll(); |
There was a problem hiding this comment.
A better pattern in this situation, would be to call UnregisterAll() from scope_exit. It would
- make it instantly clear that UnregisterAll isn't under test here.
- though it doesn't apply to this specific case as Unregister can't throw E_INVALIDARG, avoid a situation where the fuction under test does not throw as expected but the cleanup one does. The test would then pass although it should be failing.
This comment applies to all similar tests
| } | ||
|
|
||
| bool VerifyRegisterWithDisplayNameAndIcon_Unpackaged() | ||
| { |
There was a problem hiding this comment.
It's a shame that the only validation we do in this test is that Register() didn't throw. I wish we could verify that calling register actually did something useful and valid, i.e.: besides running code that didn't crash / throw...
| } | ||
|
|
||
| TEST_METHOD(VerifyRegisterWithAssetsFail) | ||
| { |
There was a problem hiding this comment.
I assume that we run a single test for packaged just to confirm that register works for packaged, and that we rely on the more complete suite of unpackaged tests to actually verify Register()'s actual behavior.
It would be nice if, we could just run the suite twice: once for unpackaged and one for packaged. It seems the only difference is whether RunTestUnpackaged or RunTest is used to launch the test. It feels like dependency injection could help here.
Not suggesting you do it now, that's probably beyond the point of this PR but that could be a nice future refactor.
There was a problem hiding this comment.
It's possible to detect at compile time if you're built with WindowsAppSDKSelfContained=true or false. At least, I think it is now in Foundation with recent changes I made to .props/.targets
Our test infrastructure does tend to build everything both ways though it was only running SelfContained=false. My PR in the Aggregator fixes that (going in soon).
Sorry limited network access at the moment. If you grip for SelfContained across build* you should find the C++ and C# symbols code can check for.
Though I suggest not messing with that until my changes go in as that has significant updates to the plumbing
| { | ||
| THROW_HR(HRESULT_FROM_WIN32(GetLastError())); | ||
| } | ||
| winrt::check_bool(DeleteFile(iconFilePath.c_str()) != FALSE); |
There was a problem hiding this comment.
NIT: DeleteFileW is preferable to DeleteFile (ensures no ambiguity to the reader)
Is winrt::check_bool() equivalent wil::RETURN_IF_WIN32_BOOL_FALSE()?
NOTE: It's weird bordering on perverse to explicitly check for == or != for values of TRUE or FALSE
| } | ||
|
|
||
| void AppNotificationManager::Register() | ||
| void AppNotificationManager::RegisterHelper(hstring const& displayName, std::wstring const& iconFilePath) |
| void Register(); | ||
|
|
||
| // For Unpackaged apps only, the caller process will be registered as the COM server. | ||
| void Register(String displayName, Windows.Foundation.Uri iconUri); |
There was a problem hiding this comment.
What happens when a Packaged app calls this? Is that an error?
jonwis
left a comment
There was a problem hiding this comment.
See note about enhancing the samples.
|
This PR will be closed and all the changes are now tracked on: #2596 |
Description: Allow developers to input DisplayName and Icon of their choice to be registered with AppNotificationManager so that these assets can show up when AppNotifications are posted or received.
Validation: Introduced unittests and validated them.