diff --git a/Samples/360VideoPlayback/cpp/Package.appxmanifest b/Samples/360VideoPlayback/cpp/Package.appxmanifest index 0ec7a420c0..ce5060bdee 100644 --- a/Samples/360VideoPlayback/cpp/Package.appxmanifest +++ b/Samples/360VideoPlayback/cpp/Package.appxmanifest @@ -11,7 +11,7 @@ 360 Video Playback C++ Sample Microsoft Corporation - Assets\StoreLogo.png + Assets\StoreLogo-sdk.png diff --git a/Samples/360VideoPlayback/cs/Common/DeviceResources.cs b/Samples/360VideoPlayback/cs/Common/DeviceResources.cs index 6edbd91011..cf5fb773c9 100644 --- a/Samples/360VideoPlayback/cs/Common/DeviceResources.cs +++ b/Samples/360VideoPlayback/cs/Common/DeviceResources.cs @@ -66,12 +66,12 @@ private void CreateDeviceIndependentResources() // Initialize Direct2D resources. var debugLevel = SharpDX.Direct2D1.DebugLevel.None; - //#if DEBUG + #if DEBUG if (DirectXHelper.SdkLayersAvailable()) { debugLevel = SharpDX.Direct2D1.DebugLevel.Information; } - //#endif + #endif // Initialize the Direct2D Factory. d2dFactory = this.ToDispose( diff --git a/Samples/360VideoPlayback/cs/Package.appxmanifest b/Samples/360VideoPlayback/cs/Package.appxmanifest index b99094015d..18cc20eb87 100644 --- a/Samples/360VideoPlayback/cs/Package.appxmanifest +++ b/Samples/360VideoPlayback/cs/Package.appxmanifest @@ -11,7 +11,7 @@ 360 Video Playback C# Sample Microsoft Corporation - Assets\StoreLogo.png + Assets\StoreLogo-sdk.png diff --git a/Samples/BackgroundTransfer/README.md b/Samples/BackgroundTransfer/README.md index 3798600f1a..f1a5c74a4b 100644 --- a/Samples/BackgroundTransfer/README.md +++ b/Samples/BackgroundTransfer/README.md @@ -64,7 +64,7 @@ For more information on network capabilities, see [How to set network capabiliti **Client:** Windows 10 -**Server:** Windows Server 2016 Technical Preview +**Server:** Windows Server 2016 **Phone:** Windows 10 diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj index b9e6cb2b0f..258359b5b6 100644 --- a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj @@ -8,8 +8,8 @@ true Windows Store 10.0 - 10.0.16299.0 - 10.0.16299.0 + 10.0.17069.0 + 10.0.17069.0 true @@ -163,6 +163,9 @@ ..\..\shared\Scenario6_RecoverableErrors.xaml + + ..\..\shared\Scenario7_DownloadReordering.xaml + @@ -177,6 +180,7 @@ + Styles\Styles.xaml @@ -220,6 +224,9 @@ ..\..\shared\Scenario6_RecoverableErrors.xaml + + ..\..\shared\Scenario7_DownloadReordering.xaml + diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj.filters b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj.filters index 7c8124bdc2..6ee73f9a7c 100644 --- a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj.filters +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/BackgroundTransfer.vcxproj.filters @@ -23,6 +23,7 @@ + @@ -35,6 +36,7 @@ + @@ -50,6 +52,7 @@ + diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Package.appxmanifest b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Package.appxmanifest index d6367cfa55..35892adf47 100644 --- a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Package.appxmanifest +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Package.appxmanifest @@ -20,7 +20,7 @@ - + diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/SampleConfiguration.cpp b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/SampleConfiguration.cpp index 2fb2247d9f..501e44c458 100644 --- a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/SampleConfiguration.cpp +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/SampleConfiguration.cpp @@ -23,4 +23,5 @@ Platform::Array^ MainPage::scenariosInner = ref new Platform::ArrayTryGetUri(serverAddressField->Text, &source)) { diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario2_Upload.xaml.cpp b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario2_Upload.xaml.cpp index 7f70c47018..a7dfcac91e 100644 --- a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario2_Upload.xaml.cpp +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario2_Upload.xaml.cpp @@ -96,7 +96,7 @@ void Scenario2_Upload::StartUpload_Click(Object^ sender, RoutedEventArgs^ e) // Validating the URI is required since it was received from an untrusted source (user input). // The URI is validated by calling TryGetUri() that will return 'false' for strings that are not valid URIs. // Note that when enabling the text box users may provide URIs to machines on the intrAnet that require the - // "Home or Work Networking" capability. + // "Private Networks (Client and Server)" capability. Uri^ uri; if (!rootPage->TryGetUri(serverAddressField->Text, &uri)) { @@ -131,7 +131,7 @@ void Scenario2_Upload::StartMultipartUpload_Click(Object^ sender, RoutedEventArg // Validating the URI is required (like TryGetUri) since it was received from an untrusted source (user input). // The URI is validated by calling TryGetUri() that will return 'false' for strings that are not valid URIs. // Note that when enabling the text box users may provide URIs to machines on the intrAnet that require the - // "Home or Work Networking" capability. + // "Private Networks (Client and Server)" capability. Uri^ uri; if (!rootPage->TryGetUri(serverAddressField->Text, &uri)) { diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario7_DownloadReordering.xaml.cpp b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario7_DownloadReordering.xaml.cpp new file mode 100644 index 0000000000..328366d218 --- /dev/null +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario7_DownloadReordering.xaml.cpp @@ -0,0 +1,295 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#include "pch.h" +#include "Scenario7_DownloadReordering.xaml.h" + +using namespace SDKTemplate; + +using namespace Concurrency; +using namespace Windows::Networking; +using namespace Windows::Networking::BackgroundTransfer; +using namespace Windows::Foundation::Collections; +using namespace Windows::Foundation; +using namespace Windows::Web; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Navigation; +using namespace Windows::UI::Xaml::Data; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Core; +using namespace Windows::Storage; +using namespace Platform::Collections; +using namespace Platform; + +DownloadOperationItem::DownloadOperationItem(DownloadOperation^ download) +{ + _download = download; + _percentComplete = 0; + _stateText = "Idle"; +} + +DownloadOperation^ DownloadOperationItem::download::get() +{ + return _download; +} + +int DownloadOperationItem::percentComplete::get() +{ + return _percentComplete; +} + +void DownloadOperationItem::percentComplete::set(int value) +{ + if (_percentComplete != value) + { + _percentComplete = value; + PropertyChanged(this, ref new PropertyChangedEventArgs("percentComplete")); + } +} + +String^ DownloadOperationItem::stateText::get() +{ + return _stateText; +} + +void DownloadOperationItem::stateText::set(String^ value) +{ + if (_stateText != value) + { + _stateText = value; + PropertyChanged(this, ref new PropertyChangedEventArgs("stateText")); + } +} + +Scenario7_DownloadReordering::Scenario7_DownloadReordering() +{ + reorderGroup = BackgroundTransferGroup::CreateGroup("{7421B969-18D4-4532-B6BD-22BDABF71C08}"); + reorderGroup->TransferBehavior = BackgroundTransferBehavior::Serialized; + _downloadCollection = ref new Vector(); + + DataContext = this; + InitializeComponent(); +} + +IObservableVector^ Scenario7_DownloadReordering::downloadCollection::get() +{ + return _downloadCollection; +} + +void Scenario7_DownloadReordering::OnNavigatedTo(NavigationEventArgs^ e) +{ + rootPage = MainPage::Current; + CancelActiveDownloadsAsync().then([this](task> previousTask) + { + try + { + previousTask.get(); + } + catch (Exception^ ex) + { + if (!IsWebException("Discovery error", ex)) + { + throw; + } + } + }); +} + +task> Scenario7_DownloadReordering::CancelActiveDownloadsAsync() +{ + // Only the downloads that belong to the transfer group used by this sample scenario will be + // canceled. + return create_task(BackgroundDownloader::GetCurrentDownloadsForTransferGroupAsync(reorderGroup)) + .then([this](IVectorView^ downloads) + { + std::vector> tasks; + + // If previous instances of this scenario started transfers that haven't completed yet, + // cancel them now so that we can start this scenario cleanly. + if (downloads->Size > 0) + { + cancellation_token_source cancellationToken; + + for (DownloadOperation^ download : downloads) { + tasks.push_back(create_task(download->AttachAsync(), cancellationToken.get_token())); + } + + cancellationToken.cancel(); + } + + return when_all(tasks.begin(), tasks.end()); + }); +} + +void Scenario7_DownloadReordering::StartDownload_Click(Object^ sender, RoutedEventArgs^ e) +{ + Button^ startButton = safe_cast(sender); + startButton->IsEnabled = false; + + // Create and start downloads. + RunDownloadsAsync().then([this, startButton]() + { + // After all downloads are complete, let the user start new ones again. + startButton->IsEnabled = true; + }, task_continuation_context::use_current()); +} + +void Scenario7_DownloadReordering::MakeCurrent_Click(Object^ sender, RoutedEventArgs e) +{ + Button^ button = safe_cast(sender); + DownloadOperationItem^ item = safe_cast(button->DataContext); + + // Make the selected operation current. + item->download->MakeCurrentInTransferGroup(); +} + +task Scenario7_DownloadReordering::RunDownloadsAsync() +{ + // Create a downloader and associate all its downloads with the transfer group used for this + // scenario. + BackgroundDownloader^ downloader = ref new BackgroundDownloader(); + downloader->TransferGroup = reorderGroup; + + // Validating the URI is required since it was received from an untrusted source (user input). + // The URI is validated by calling rootPage->TryGetUri() that will return 'false' for strings + // that are not valid URIs. + // Note that when enabling the text box users may provide URIs to machines on the intranet that + // require the "Private Networks (Client and Server)" capability. + String^ remoteAddress = StringTrimmer::Trim(remoteAddressField->Text); + Uri^ tmpUri; + if (!rootPage->TryGetUri(remoteAddress, &tmpUri)) + { + return task_from_result(); + } + + String^ fileName = StringTrimmer::Trim(fileNameField->Text); + if (fileName->IsEmpty()) + { + rootPage->NotifyUser("A local file name is required.", NotifyType::ErrorMessage); + return task_from_result(); + } + + // Try to create some downloads. + std::vector> createDownloadTasks; + for (int i = 0; i < NumDownloads; i++) + { + String^ currRemoteAddress = remoteAddress + "?id=" + i.ToString(); + String^ currFileName = i.ToString() + "." + fileName; + createDownloadTasks.push_back(CreateDownloadAsync(downloader, currRemoteAddress, currFileName)); + } + + return when_all(createDownloadTasks.begin(), createDownloadTasks.end()) + .then([this](std::vector downloads) + { + // Once all downloads have been created, start them. + _downloadCollection->Clear(); + std::vector> downloadTasks; + for (DownloadOperation^ download : downloads) + { + DownloadOperationItem^ item = ref new DownloadOperationItem(download); + downloadTasks.push_back(DownloadAsync(item)); + _downloadCollection->Append(item); + } + return when_all(downloadTasks.begin(), downloadTasks.end()); + }); +} + +task Scenario7_DownloadReordering::CreateDownloadAsync( + BackgroundDownloader^ downloader, + String^ remoteAddress, + String^ fileName) +{ + return create_task(KnownFolders::PicturesLibrary->CreateFileAsync( + fileName, + CreationCollisionOption::GenerateUniqueName)) + .then([this, downloader, remoteAddress](StorageFile^ destinationFile) + { + Uri^ source = ref new Uri(remoteAddress); + return downloader->CreateDownload(source, destinationFile); + }); +} + + +task Scenario7_DownloadReordering::DownloadAsync(DownloadOperationItem^ item) +{ + IAsyncOperationWithProgress^ async = item->download->StartAsync(); + async->Progress = ref new AsyncOperationProgressHandler( + [this, item]( + IAsyncOperationWithProgress^ operation, + DownloadOperation^ download) + { + DownloadProgress(item); + }); + return create_task(async).then([this, item](DownloadOperation^ download) + { + item->stateText = item->download->Progress.Status.ToString(); + rootPage->NotifyUser( + "Downloading " + item->download->ResultFile->Name + " completed.", + NotifyType::StatusMessage); + }).then([this, item](task previousTask) + { + try + { + previousTask.get(); + } + catch (Exception^ ex) + { + // Ignore canceled downloads since they are not displayed. + if (item->download->Progress.Status != BackgroundTransferStatus::Canceled) + { + // Ensure that we reach 100% even for errors. + item->percentComplete = 100; + item->stateText = item->download->Progress.Status.ToString(); + if (!IsWebException("Execution error", ex, item->download)) + { + throw; + } + } + } + }); +} + +void Scenario7_DownloadReordering::DownloadProgress(DownloadOperationItem^ item) +{ + BackgroundDownloadProgress currentProgress = item->download->Progress; + BackgroundTransferStatus status = currentProgress.Status; + int percentComplete = 0; + + if (currentProgress.TotalBytesToReceive > 0) + { + percentComplete = (int)((currentProgress.BytesReceived * 100) / currentProgress.TotalBytesToReceive); + } + + Dispatcher->RunAsync(CoreDispatcherPriority::Normal, ref new DispatchedHandler( + [this, item, status, percentComplete]() + { + item->stateText = status.ToString(); + item->percentComplete = percentComplete; + })); +} + +bool Scenario7_DownloadReordering::IsWebException(String^ title, Exception^ ex, DownloadOperation^ download) +{ + WebErrorStatus error = BackgroundTransferError::GetStatus(ex->HResult); + bool result = (error != WebErrorStatus::Unknown); + String^ message = result ? error.ToString() : ex->Message; + + if (download == nullptr) + { + rootPage->NotifyUser(title + ": " + message, NotifyType::ErrorMessage); + } + else + { + rootPage->NotifyUser(download->ResultFile->Name + " - " + title + ": " + message, NotifyType::ErrorMessage); + } + + return result; +} diff --git a/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario7_DownloadReordering.xaml.h b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario7_DownloadReordering.xaml.h new file mode 100644 index 0000000000..55eb4d2f7b --- /dev/null +++ b/Samples/BackgroundTransfer/cpp/BackgroundTransfer/Scenario7_DownloadReordering.xaml.h @@ -0,0 +1,87 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +#pragma once + +#include "pch.h" +#include "Scenario7_DownloadReordering.g.h" +#include "MainPage.xaml.h" + +namespace SDKTemplate +{ + // Class for each download and its row in the UI. + public ref class DownloadOperationItem sealed : Windows::UI::Xaml::Data::INotifyPropertyChanged + { + public: + DownloadOperationItem(Windows::Networking::BackgroundTransfer::DownloadOperation^ download); + + virtual event Windows::UI::Xaml::Data::PropertyChangedEventHandler^ PropertyChanged; + + property Windows::Networking::BackgroundTransfer::DownloadOperation^ download + { + Windows::Networking::BackgroundTransfer::DownloadOperation^ get(); + } + + property int percentComplete + { + int get(); + void set(int value); + } + + property Platform::String^ stateText + { + Platform::String^ get(); + void set(Platform::String^ value); + } + + private: + Windows::Networking::BackgroundTransfer::DownloadOperation^ _download; + int _percentComplete; + Platform::String^ _stateText; + }; + + [Windows::Foundation::Metadata::WebHostHidden] + public ref class Scenario7_DownloadReordering sealed + { + public: + Scenario7_DownloadReordering(); + + property Windows::Foundation::Collections::IObservableVector^ downloadCollection + { + Windows::Foundation::Collections::IObservableVector^ get(); + } + protected: + virtual void OnNavigatedTo(Windows::UI::Xaml::Navigation::NavigationEventArgs^ e) override; + + private: + Concurrency::task> + CancelActiveDownloadsAsync(); + void StartDownload_Click(Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e); + void MakeCurrent_Click(Object^ sender, Windows::UI::Xaml::RoutedEventArgs e); + Concurrency::task RunDownloadsAsync(); + Concurrency::task CreateDownloadAsync( + Windows::Networking::BackgroundTransfer::BackgroundDownloader^ downloader, + Platform::String^ remoteAddress, + Platform::String^ fileName); + Concurrency::task DownloadAsync(DownloadOperationItem^ item); + void DownloadProgress(DownloadOperationItem^ item); + bool Scenario7_DownloadReordering::IsWebException( + Platform::String^ title, + Platform::Exception^ ex, + Windows::Networking::BackgroundTransfer::DownloadOperation^ download = nullptr); + + const int NumDownloads = 5; + + MainPage^ rootPage; + Windows::Networking::BackgroundTransfer::BackgroundTransferGroup^ reorderGroup; + Windows::Foundation::Collections::IObservableVector^ _downloadCollection; + }; +} diff --git a/Samples/BackgroundTransfer/cpp/Tasks/Tasks.vcxproj b/Samples/BackgroundTransfer/cpp/Tasks/Tasks.vcxproj index f3bcb5343b..b723d12af0 100644 --- a/Samples/BackgroundTransfer/cpp/Tasks/Tasks.vcxproj +++ b/Samples/BackgroundTransfer/cpp/Tasks/Tasks.vcxproj @@ -36,8 +36,8 @@ true Windows Store 10.0 - 10.0.16299.0 - 10.0.16299.0 + 10.0.17069.0 + 10.0.17069.0 true diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/BackgroundTransfer.csproj b/Samples/BackgroundTransfer/cs/BackgroundTransfer/BackgroundTransfer.csproj index 153ae32c63..f2e76c229d 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/BackgroundTransfer.csproj +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/BackgroundTransfer.csproj @@ -11,8 +11,8 @@ BackgroundTransfer en-US UAP - 10.0.16299.0 - 10.0.16299.0 + 10.0.17069.0 + 10.0.17069.0 14 true 512 @@ -121,6 +121,9 @@ Scenario6_RecoverableErrors.xaml + + Scenario7_DownloadReordering.xaml + @@ -168,6 +171,11 @@ MSBuild:Compile Designer + + Scenario7_DownloadReordering.xaml + MSBuild:Compile + Designer + Styles\Styles.xaml MSBuild:Compile diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Package.appxmanifest b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Package.appxmanifest index 5743cc2750..521585e522 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Package.appxmanifest +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Package.appxmanifest @@ -20,7 +20,7 @@ - + diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/SampleConfiguration.cs b/Samples/BackgroundTransfer/cs/BackgroundTransfer/SampleConfiguration.cs index e76d54928d..0f2fc2c18d 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/SampleConfiguration.cs +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/SampleConfiguration.cs @@ -26,7 +26,8 @@ public partial class MainPage : Page new Scenario() { Title="Completion Notifications", ClassType=typeof(Scenario3_Notifications)}, new Scenario() { Title="Completion Groups", ClassType=typeof(Scenario4_CompletionGroups)}, new Scenario() { Title="Random Access Downloads", ClassType=typeof(Scenario5_RandomAccess)}, - new Scenario() { Title="Recoverable Errors", ClassType=typeof(Scenario6_RecoverableErrors)} + new Scenario() { Title="Recoverable Errors", ClassType=typeof(Scenario6_RecoverableErrors)}, + new Scenario() { Title="Download Reordering", ClassType=typeof(Scenario7_DownloadReordering)}, }; } diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario1_Download.xaml.cs b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario1_Download.xaml.cs index 22291f30ab..cad349dda7 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario1_Download.xaml.cs +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario1_Download.xaml.cs @@ -117,7 +117,7 @@ private async void StartDownload(BackgroundTransferPriority priority) // Validating the URI is required since it was received from an untrusted source (user input). // The URI is validated by calling Uri.TryCreate() that will return 'false' for strings that are not valid URIs. // Note that when enabling the text box users may provide URIs to machines on the intrAnet that require - // the "Home or Work Networking" capability. + // the "Private Networks (Client and Server)" capability. Uri source; if (!Uri.TryCreate(serverAddressField.Text.Trim(), UriKind.Absolute, out source)) { diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario2_Upload.xaml.cs b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario2_Upload.xaml.cs index a36d43fb85..f4565e6cef 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario2_Upload.xaml.cs +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario2_Upload.xaml.cs @@ -115,7 +115,7 @@ private void StartUpload_Click(object sender, RoutedEventArgs e) // Validating the URI is required since it was received from an untrusted source (user input). // The URI is validated by calling Uri.TryCreate() that will return 'false' for strings that are not valid URIs. // Note that when enabling the text box users may provide URIs to machines on the intrAnet that require - // the "Home or Work Networking" capability. + // the "Private Networks (Client and Server)" capability. Uri uri; if (!Uri.TryCreate(serverAddressField.Text.Trim(), UriKind.Absolute, out uri)) { @@ -163,7 +163,7 @@ private void StartMultipartUpload_Click(object sender, RoutedEventArgs e) // box validating the URI is required since it was received from an untrusted source (user input). // The URI is validated by calling Uri.TryCreate() that will return 'false' for strings that are not valid URIs. // Note that when enabling the text box users may provide URIs to machines on the intrAnet that require - // the "Home or Work Networking" capability. + // the "Private Networks (Client and Server)" capability. Uri uri; if (!Uri.TryCreate(serverAddressField.Text.Trim(), UriKind.Absolute, out uri)) { diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario3_Notifications.xaml.cs b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario3_Notifications.xaml.cs index a2ce1a95f5..90150a7dbb 100644 --- a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario3_Notifications.xaml.cs +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario3_Notifications.xaml.cs @@ -188,7 +188,7 @@ private async Task RunDownloadsAsync(BackgroundDownloader downloader, ScenarioTy // Validating the URI is required since it was received from an untrusted source (user input). // The URI is validated by calling Uri.TryCreate() that will return 'false' for strings that are not valid URIs. // Note that when enabling the text box users may provide URIs to machines on the intrAnet that require - // the "Home or Work Networking" capability. + // the "Private Networks (Client and Server)" capability. if (!Uri.TryCreate(serverAddressField.Text.Trim(), UriKind.Absolute, out baseUri)) { rootPage.NotifyUser("Invalid URI.", NotifyType.ErrorMessage); diff --git a/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario7_DownloadReordering.xaml.cs b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario7_DownloadReordering.xaml.cs new file mode 100644 index 0000000000..305f86f133 --- /dev/null +++ b/Samples/BackgroundTransfer/cs/BackgroundTransfer/Scenario7_DownloadReordering.xaml.cs @@ -0,0 +1,321 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Windows.Networking.BackgroundTransfer; +using Windows.Storage; +using Windows.UI.Core; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Navigation; +using Windows.Web; + +namespace SDKTemplate +{ + // Class for each download and its row in the UI. + public class DownloadOperationItem : INotifyPropertyChanged + { + public DownloadOperationItem(DownloadOperation download) + { + _download = download; + _percentComplete = 0; + _stateText = "Idle"; + } + + public event PropertyChangedEventHandler PropertyChanged; + + public DownloadOperation download + { + get + { + return _download; + } + } + + public int percentComplete + { + get + { + return _percentComplete; + } + + set + { + _percentComplete = value; + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs("percentComplete")); + } + } + } + + public string stateText + { + get + { + return _stateText; + } + + set + { + _stateText = value; + if (PropertyChanged != null) + { + PropertyChanged(this, new PropertyChangedEventArgs("stateText")); + } + } + } + + private DownloadOperation _download; + private int _percentComplete; + private string _stateText; + } + + public sealed partial class Scenario7_DownloadReordering : Page + { + private const int NumDownloads = 5; + + private MainPage rootPage; + private BackgroundTransferGroup reorderGroup; + public ObservableCollection downloadCollection { get; } = + new ObservableCollection(); + + public Scenario7_DownloadReordering() + { + reorderGroup = BackgroundTransferGroup.CreateGroup("{7421B969-18D4-4532-B6BD-22BDABF71C08}"); + reorderGroup.TransferBehavior = BackgroundTransferBehavior.Serialized; + this.DataContext = this; + this.InitializeComponent(); + } + + protected async override void OnNavigatedTo(NavigationEventArgs e) + { + rootPage = MainPage.Current; + await CancelActiveDownloadsAsync(); + } + + private async Task CancelActiveDownloadsAsync() + { + // Only the downloads that belong to the transfer group used by this sample scenario + // will be canceled. + IReadOnlyList downloads = null; + try + { + downloads = await BackgroundDownloader.GetCurrentDownloadsForTransferGroupAsync(reorderGroup); + } + catch (Exception ex) + { + if (!IsWebException("Discovery error", ex)) + { + throw; + } + return; + } + + // If previous instances of this scenario started transfers that haven't completed yet, + // cancel them now so that we can start this scenario cleanly. + if (downloads.Count > 0) + { + CancellationTokenSource cancellationToken = new CancellationTokenSource(); + cancellationToken.Cancel(); + + Task[] tasks = new Task[downloads.Count]; + for (int i = 0; i < downloads.Count; i++) + { + tasks[i] = downloads[i].AttachAsync().AsTask(cancellationToken.Token); + } + + // Cancel each download and ignore the cancellation exception. + try + { + await Task.WhenAll(tasks); + } + catch (TaskCanceledException) + { + } + } + } + + private async void StartDownload_Click(object sender, RoutedEventArgs e) + { + startDownloadButton.IsEnabled = false; + + // Create and start downloads. + await RunDownloadsAsync(); + + // After all downloads are complete, let the user start new ones again. + startDownloadButton.IsEnabled = true; + } + + private void MakeCurrent_Click(object sender, RoutedEventArgs e) + { + Button button = sender as Button; + DownloadOperationItem item = button.DataContext as DownloadOperationItem; + + // Make the selected operation current. + item.download.MakeCurrentInTransferGroup(); + } + + private async Task RunDownloadsAsync() + { + // Create a downloader and associate all its downloads with the transfer group used for + // this scenario. + BackgroundDownloader downloader = new BackgroundDownloader(); + downloader.TransferGroup = reorderGroup; + + // Validating the URI is required since it was received from an untrusted source (user + // input). + // The URI is validated by calling Uri.TryCreate() that will return 'false' for strings + // that are not valid URIs. + // Note that when enabling the text box users may provide URIs to machines on the + // intranet that require the "Private Networks (Client and Server)" capability. + string remoteAddress = remoteAddressField.Text.Trim(); + Uri tmpUri; + if (!Uri.TryCreate(remoteAddress, UriKind.Absolute, out tmpUri)) + { + rootPage.NotifyUser("Invalid URI.", NotifyType.ErrorMessage); + return; + } + + string fileName = fileNameField.Text.Trim(); + if (string.IsNullOrWhiteSpace(fileName)) + { + rootPage.NotifyUser("A local file name is required.", NotifyType.ErrorMessage); + return; + } + + // Try to create some downloads. + DownloadOperation[] downloads = new DownloadOperation[NumDownloads]; + try + { + for (int i = 0; i < NumDownloads; i++) + { + string currRemoteAddress = $"{remoteAddress}?id={i}"; + string currFileName = $"{i}.{fileName}"; + downloads[i] = await CreateDownload(downloader, currRemoteAddress, currFileName); + } + } + catch (FileNotFoundException ex) + { + rootPage.NotifyUser($"Error while creating file: {ex.Message}", NotifyType.ErrorMessage); + return; + } + + // Once all downloads have been created, start them. + Task[] downloadTasks = new Task[downloads.Length]; + downloadCollection.Clear(); + for (int i = 0; i < downloads.Length; i++) + { + DownloadOperationItem item = new DownloadOperationItem(downloads[i]); + downloadTasks[i] = DownloadAsync(item); + downloadCollection.Add(item); + } + + await Task.WhenAll(downloadTasks); + } + + private async Task CreateDownload( + BackgroundDownloader downloader, + string remoteAddress, + string fileName) + { + Uri source = new Uri(remoteAddress); + + StorageFile destinationFile; + try + { + destinationFile = await KnownFolders.PicturesLibrary.CreateFileAsync( + fileName, + CreationCollisionOption.GenerateUniqueName); + } + catch (FileNotFoundException ex) + { + rootPage.NotifyUser($"Error while creating file: {ex.Message}", NotifyType.ErrorMessage); + throw; + } + + return downloader.CreateDownload(source, destinationFile); + } + + private async Task DownloadAsync(DownloadOperationItem item) + { + Progress progressCallback = new Progress( + (operation) => DownloadProgress(item)); + + try + { + await item.download.StartAsync().AsTask(progressCallback); + + item.stateText = item.download.Progress.Status.ToString(); + rootPage.NotifyUser( + $"Downloading {item.download.ResultFile.Name} completed.", + NotifyType.StatusMessage); + } + catch (Exception ex) + { + // Ignore canceled downloads since they are not displayed. + if (item.download.Progress.Status != BackgroundTransferStatus.Canceled) + { + // Ensure that we reach 100% even for errors. + item.percentComplete = 100; + item.stateText = item.download.Progress.Status.ToString(); + if (!IsWebException("Execution error", ex, item.download)) + { + throw; + } + } + } + } + + private void DownloadProgress(DownloadOperationItem item) + { + BackgroundDownloadProgress currentProgress = item.download.Progress; + BackgroundTransferStatus status = currentProgress.Status; + int percentComplete = 0; + + if (currentProgress.TotalBytesToReceive > 0) + { + percentComplete = (int)((currentProgress.BytesReceived * 100) / currentProgress.TotalBytesToReceive); + } + + var ignore = this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + item.stateText = status.ToString(); + item.percentComplete = percentComplete; + }); + } + + private bool IsWebException(string title, Exception ex, DownloadOperation download = null) + { + WebErrorStatus error = BackgroundTransferError.GetStatus(ex.HResult); + bool result = (error != WebErrorStatus.Unknown); + string message = result ? error.ToString() : ex.Message; + + if (download == null) + { + rootPage.NotifyUser($"{title}: {message}", NotifyType.ErrorMessage); + } + else + { + rootPage.NotifyUser($"{download.ResultFile.Name} - {title}: {message}", NotifyType.ErrorMessage); + } + + return result; + } + } +} diff --git a/Samples/BackgroundTransfer/cs/Tasks/Tasks.csproj b/Samples/BackgroundTransfer/cs/Tasks/Tasks.csproj index b8590a7397..c1ef57c12f 100644 --- a/Samples/BackgroundTransfer/cs/Tasks/Tasks.csproj +++ b/Samples/BackgroundTransfer/cs/Tasks/Tasks.csproj @@ -11,8 +11,8 @@ Tasks en-US UAP - 10.0.16299.0 - 10.0.16299.0 + 10.0.17069.0 + 10.0.17069.0 14 true 512 diff --git a/Samples/BackgroundTransfer/js/BackgroundTransfer.jsproj b/Samples/BackgroundTransfer/js/BackgroundTransfer.jsproj index d7bdbcf533..8f762b27f6 100644 --- a/Samples/BackgroundTransfer/js/BackgroundTransfer.jsproj +++ b/Samples/BackgroundTransfer/js/BackgroundTransfer.jsproj @@ -45,8 +45,8 @@ UAP - 10.0.16299.0 - 10.0.16299.0 + 10.0.17069.0 + 10.0.17069.0 $(VersionNumberMajor).$(VersionNumberMinor) en-US @@ -87,6 +87,7 @@ + @@ -95,6 +96,7 @@ + Microsoft.WinJS.4.0\css\ui-dark.css diff --git a/Samples/BackgroundTransfer/js/html/scenario7_DownloadReordering.html b/Samples/BackgroundTransfer/js/html/scenario7_DownloadReordering.html new file mode 100644 index 0000000000..dd69c8280d --- /dev/null +++ b/Samples/BackgroundTransfer/js/html/scenario7_DownloadReordering.html @@ -0,0 +1,65 @@ + + + + + + + + +

Description:

+
Download reordering
+

+ Transfers which belong to the same transfer group can be reordered after they have been started. +

+

+ + +

+

+ + +

+

+ +

+

+ Select a pending transfer to reorder the downloads. +

+

+ + + idle +

+

+ + + idle +

+

+ + + idle +

+

+ + + idle +

+

+ + + idle +

+ + diff --git a/Samples/BackgroundTransfer/js/js/sample-configuration.js b/Samples/BackgroundTransfer/js/js/sample-configuration.js index b8f7c4f50c..444c8d5c59 100644 --- a/Samples/BackgroundTransfer/js/js/sample-configuration.js +++ b/Samples/BackgroundTransfer/js/js/sample-configuration.js @@ -19,7 +19,8 @@ { url: "/html/scenario3_Notifications.html", title: "Completion Notifications" }, { url: "/html/scenario4_CompletionGroups.html", title: "Completion Groups" }, { url: "/html/scenario5_RandomAccess.html", title: "Random Access" }, - { url: "/html/scenario6_RecoverableErrors.html", title: "Recoverable Errors" } + { url: "/html/scenario6_RecoverableErrors.html", title: "Recoverable Errors" }, + { url: "/html/scenario7_DownloadReordering.html", title: "Download Reordering" } ]; // Look up the name for an enumeration member. diff --git a/Samples/BackgroundTransfer/js/js/scenario1_Download.js b/Samples/BackgroundTransfer/js/js/scenario1_Download.js index 77980735fb..4a7f92deb5 100644 --- a/Samples/BackgroundTransfer/js/js/scenario1_Download.js +++ b/Samples/BackgroundTransfer/js/js/scenario1_Download.js @@ -44,32 +44,6 @@ // Global array used to persist operations. var downloadOperations = []; - function backgroundTransferStatusToString(transferStatus) { - switch (transferStatus) - { - case BackgroundTransfer.BackgroundTransferStatus.idle: - return "Idle"; - case BackgroundTransfer.BackgroundTransferStatus.running: - return "Running"; - case BackgroundTransfer.BackgroundTransferStatus.pausedByApplication: - return "PausedByApplication"; - case BackgroundTransfer.BackgroundTransferStatus.pausedCostedNetwork: - return "PausedCostedNetwork"; - case BackgroundTransfer.BackgroundTransferStatus.pausedNoNetwork: - return "PausedNoNetwork"; - case BackgroundTransfer.BackgroundTransferStatus.completed: - return "Completed"; - case BackgroundTransfer.BackgroundTransferStatus.canceled: - return "Canceled"; - case BackgroundTransfer.BackgroundTransferStatus.error: - return "Error"; - case BackgroundTransfer.BackgroundTransferStatus.pausedSystemPolicy: - return "PausedSystemPolicy"; - default: - return "Unknown"; - } - } - // Class associated with each download. function DownloadOperation() { var download = null; @@ -100,7 +74,8 @@ this.load = function (loadedDownload) { download = loadedDownload; printLog("Discovered background download: " + download.guid + ", Status: " + - backgroundTransferStatusToString(download.progress.status) + "
"); + SdkSample.lookupEnumName(BackgroundTransfer.BackgroundTransferStatus, download.progress.status) + + "
"); promise = download.attachAsync().then(complete, error, progress); }; @@ -128,7 +103,8 @@ printLog("Resumed: " + download.guid + "
"); } else { printLog("Skipped: " + download.guid + ", Status: " + - backgroundTransferStatusToString(currentProgress.status) + "
"); + SdkSample.lookupEnumName(BackgroundTransfer.BackgroundTransferStatus, currentProgress.status) + + "
"); } } }; @@ -146,7 +122,8 @@ printLog("Paused: " + download.guid + "
"); } else { printLog("Skipped: " + download.guid + ", Status: " + - backgroundTransferStatusToString(currentProgress.status) + "
"); + SdkSample.lookupEnumName(BackgroundTransfer.BackgroundTransferStatus, currentProgress.status) + + "
"); } } }; @@ -173,7 +150,8 @@ var currentProgress = download.progress; printLog("Progress: " + download.guid + ", Status: " + - backgroundTransferStatusToString(currentProgress.status) + "
"); + SdkSample.lookupEnumName(BackgroundTransfer.BackgroundTransferStatus, currentProgress.status) + + "
"); var percent = 100; if (currentProgress.totalBytesToReceive > 0) { diff --git a/Samples/BackgroundTransfer/js/js/scenario2_Upload.js b/Samples/BackgroundTransfer/js/js/scenario2_Upload.js index 93a56af409..7b3528187c 100644 --- a/Samples/BackgroundTransfer/js/js/scenario2_Upload.js +++ b/Samples/BackgroundTransfer/js/js/scenario2_Upload.js @@ -61,31 +61,6 @@ var maxUploadFileSize = 100 * 1024 * 1024; // 100 MB (arbitrary limit chosen for this sample) - function backgroundTransferStatusToString(transferStatus) { - switch (transferStatus) { - case BackgroundTransfer.BackgroundTransferStatus.idle: - return "Idle"; - case BackgroundTransfer.BackgroundTransferStatus.running: - return "Running"; - case BackgroundTransfer.BackgroundTransferStatus.pausedByApplication: - return "PausedByApplication"; - case BackgroundTransfer.BackgroundTransferStatus.pausedCostedNetwork: - return "PausedCostedNetwork"; - case BackgroundTransfer.BackgroundTransferStatus.pausedNoNetwork: - return "PausedNoNetwork"; - case BackgroundTransfer.BackgroundTransferStatus.completed: - return "Completed"; - case BackgroundTransfer.BackgroundTransferStatus.canceled: - return "Canceled"; - case BackgroundTransfer.BackgroundTransferStatus.error: - return "Error"; - case BackgroundTransfer.BackgroundTransferStatus.pausedSystemPolicy: - return "PausedSystemPolicy"; - default: - return "Unknown"; - } - } - // Class associated with each upload. function UploadOperation() { var upload = null; @@ -132,7 +107,8 @@ this.load = function (loadedUpload) { upload = loadedUpload; printLog("Discovered background upload: " + upload.guid + " , Status: " + - backgroundTransferStatusToString(upload.progress.status) + "
"); + SdkSample.lookupEnumName(BackgroundTransfer.BackgroundTransferStatus, upload.progress.status) + + "
"); promise = upload.attachAsync().then(complete, error, progress); }; @@ -169,7 +145,8 @@ var currentProgress = upload.progress; printLog("Progress: " + upload.guid + ", Status: " + - backgroundTransferStatusToString(currentProgress.status) + "
"); + SdkSample.lookupEnumName(BackgroundTransfer.BackgroundTransferStatus, currentProgress.status) + + "
"); var percent = 100; if (currentProgress.totalBytesToSend > 0) { diff --git a/Samples/BackgroundTransfer/js/js/scenario7_DownloadReordering.js b/Samples/BackgroundTransfer/js/js/scenario7_DownloadReordering.js new file mode 100644 index 0000000000..ff38d6df0b --- /dev/null +++ b/Samples/BackgroundTransfer/js/js/scenario7_DownloadReordering.js @@ -0,0 +1,220 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +(function () { + "use strict"; + + var BackgroundTransfer = Windows.Networking.BackgroundTransfer; + + // Class for each download and its row in the UI. + function DownloadCollectionItem(i) { + var makeCurrentButton = document.getElementById("makeCurrentButton" + i); + var percentCompleteInput = document.getElementById("percentCompleteInput" + i); + var stateTextSpan = document.getElementById("stateTextSpan" + i); + var download = null; + + this.startAsync = function (dl) { + download = dl; + + makeCurrentButton.addEventListener("click", makeCurrent_click); + makeCurrentButton.disabled = false; + percentCompleteInput.value = 0; + stateTextSpan.innerText = "idle"; + + return download.startAsync().then(complete, error, progress); + } + + this.reset = function () { + makeCurrentButton.disabled = true; + download = null; + } + + function makeCurrent_click() { + // Make the selected operation current. + download.makeCurrentInTransferGroup(); + } + + function complete() { + stateTextSpan.innerText = SdkSample.lookupEnumName( + BackgroundTransfer.BackgroundTransferStatus, + download.progress.status); + WinJS.log(`Downloading ${download.resultFile.name} completed.`, "sample", "status"); + }; + + function error(err) { + // Ignore canceled downloads since they are not displayed. + if (download.progress.status != BackgroundTransfer.BackgroundTransferStatus.canceled) { + // Ensure that we reach 100% even for errors. + percentCompleteInput.value = 100; + stateTextSpan.innerText = SdkSample.lookupEnumName( + BackgroundTransfer.BackgroundTransferStatus, + download.progress.status); + if (!isWebException("Execution error", err, download)) { + return WinJS.Promise.wrapError(err); + } + } + } + + function progress() { + var currentProgress = download.progress; + var percentComplete = 0; + + if (currentProgress.totalBytesToReceive > 0) { + percentComplete = Math.floor( + (currentProgress.bytesReceived * 100) / currentProgress.totalBytesToReceive); + } + + percentCompleteInput.value = percentComplete; + stateTextSpan.innerText = SdkSample.lookupEnumName( + BackgroundTransfer.BackgroundTransferStatus, + currentProgress.status); + } + } + + const NumDownloads = 5; + + var remoteAddressField; + var fileNameField; + var startDownloadButton; + + var reorderGroup; + var downloadCollection; + + var page = WinJS.UI.Pages.define("/html/scenario7_DownloadReordering.html", { + ready: function (element, options) { + remoteAddressField = document.getElementById("remoteAddressField"); + fileNameField = document.getElementById("fileNameField"); + startDownloadButton = document.getElementById("startDownloadButton"); + startDownloadButton.addEventListener("click", startDownload_click); + + reorderGroup = BackgroundTransfer.BackgroundTransferGroup.createGroup( + "{7421B969-18D4-4532-B6BD-22BDABF71C08}"); + reorderGroup.transferBehavior = BackgroundTransfer.BackgroundTransferBehavior.serialized; + downloadCollection = []; + for (var i = 0; i < NumDownloads; i++) { + downloadCollection.push(new DownloadCollectionItem(i)); + } + + cancelActiveDownloadsAsync().done(); + } + }); + + function cancelActiveDownloadsAsync() { + // Only the downloads that belong to the transfer group used by this sample scenario will be + // canceled. + return BackgroundTransfer.BackgroundDownloader.getCurrentDownloadsForTransferGroupAsync(reorderGroup) + .then(function (downloads) { + // If previous sample instances/scenarios started transfers that haven't completed yet, + // cancel them now so that we can start this scenario cleanly. + if (downloads.length > 0) { + var promises = downloads.map(function (download) { + // Cancel each download and ignore the cancellation exception. + var downloadPromise = download.attachAsync(); + downloadPromise.cancel(); + return downloadPromise.then(null, function (err) { + if (download.progress.status != BackgroundTransfer.BackgroundTransferStatus.canceled) { + if (!isWebException("Discovery error", err, null)) { + return WinJS.Promise.wrapError(err); + } + } + }); + }) + return WinJS.Promise.join(promises); + } + }); + } + + function startDownload_click() { + startDownloadButton.disabled = true; + + // Create and start downloads. + runDownloadAsync(function () { + // After all downloads are complete, disable the downloads and let the user start new + // ones again. + downloadCollection.forEach(function (downloadCollectionItem) { + downloadCollectionItem.reset(); + }); + startDownloadButton.disabled = false; + }); + } + + function runDownloadAsync(done_callback) { + // Create a downloader and associate all its downloads with the transfer group used for this + // scenario. + var downloader = new BackgroundTransfer.BackgroundDownloader(); + downloader.transferGroup = reorderGroup; + + // Validating the URI is required since it was received from an untrusted source (user + // input). + // The URI is validated by calling Windows.Foundation.Uri that will throw an exception for + // strings that are not valid URIs. + // Note that when enabling the text box users may provide URIs to machines on the intranet + // that require the "Private Networks (Client and Server)" capability. + var remoteAddress = remoteAddressField.value.trim(); + try { + var uri = new Windows.Foundation.Uri(remoteAddress); + } + catch (error) { + WinJS.log("Invalid URI.", "sample", "error"); + return; + } + + var fileName = fileNameField.value.trim(); + if (fileName == "") { + WinJS.log("A local file name is required.", "sample", "error"); + return; + } + + // Try to create some downloads. + var createDownloadPromises = []; + for (var i = 0; i < NumDownloads; i++) { + createDownloadPromises.push( + createDownloadAsync(downloader, remoteAddress + "?id=" + i, i + "." + fileName)); + } + + return WinJS.Promise.join(createDownloadPromises).then(function (downloads) { + // Once all downloads have been created, start them. + var downloadPromises = []; + for (var i = 0; i < downloads.length; i++) { + var downloadCollectionItem = downloadCollection[i]; + downloadPromises.push(downloadCollectionItem.startAsync(downloads[i])); + } + + WinJS.Promise.join(downloadPromises).done(done_callback); + }); + } + + function createDownloadAsync(downloader, remoteAddress, fileName) { + return Windows.Storage.KnownFolders.picturesLibrary.createFileAsync( + fileName, + Windows.Storage.CreationCollisionOption.generateUniqueName).then(function (destinationFile) { + var source = new Windows.Foundation.Uri(remoteAddress); + return downloader.createDownload(source, destinationFile); + }, function (e) { + WinJS.log(`Error while creating file: ${e.message}`, "sample", "error"); + } + ); + } + + function isWebException(title, ex, download) { + var error = BackgroundTransfer.BackgroundTransferError.getStatus(ex.number); + var result = (error != Windows.Web.WebErrorStatus.unknown); + var message = result ? SdkSample.lookupEnumName(Windows.Web.WebErrorStatus, error) : ex.message; + + if (download == null) { + WinJS.log(`${title}: ${message}`, "sample", "error"); + } else { + WinJS.log(`${download.resultFile.name} - ${title}: ${message}`, "sample", "error"); + } + + return result; + } +})(); diff --git a/Samples/BackgroundTransfer/js/package.appxmanifest b/Samples/BackgroundTransfer/js/package.appxmanifest index bee023ac82..5e848d4806 100644 --- a/Samples/BackgroundTransfer/js/package.appxmanifest +++ b/Samples/BackgroundTransfer/js/package.appxmanifest @@ -19,7 +19,7 @@ - + diff --git a/Samples/BackgroundTransfer/shared/Scenario7_DownloadReordering.xaml b/Samples/BackgroundTransfer/shared/Scenario7_DownloadReordering.xaml new file mode 100644 index 0000000000..daef808374 --- /dev/null +++ b/Samples/BackgroundTransfer/shared/Scenario7_DownloadReordering.xaml @@ -0,0 +1,41 @@ + + + + + + Transfers which belong to the same transfer group can be reordered after they have been started. + + + + + + + +
+ + +
+ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Samples/Ink/js/js/sample-configuration.js b/Samples/Ink/js/js/sample-configuration.js index dc3ad607f3..d5276195ac 100644 --- a/Samples/Ink/js/js/sample-configuration.js +++ b/Samples/Ink/js/js/sample-configuration.js @@ -6,7 +6,8 @@ var sampleTitle = "Ink sample"; var scenarios = [ - { url: "/html/scenario1.html", title: "Scenario 1" } + { url: "/html/scenario1.html", title: "Scenario 1" }, + { url: "/html/scenario2.html", title: "Scenario 2" } ]; WinJS.Namespace.define("SdkSample", { diff --git a/Samples/Ink/js/js/scenario2.js b/Samples/Ink/js/js/scenario2.js new file mode 100644 index 0000000000..287c786a37 --- /dev/null +++ b/Samples/Ink/js/js/scenario2.js @@ -0,0 +1,205 @@ +//// Copyright (c) Microsoft Corporation. All rights reserved + +// Sample app demonstrating the use of InkPresenter APIs. + +(function () { + "use strict"; + + // abbreviations + var Inking = Windows.UI.Input.Inking; + var InkInputProcessingMode = Inking.InkInputProcessingMode; + + function displayStatus(message) { + WinJS.log && WinJS.log(message, "sample", "status"); + } + + function displayError(message) { + WinJS.log && WinJS.log(message, "sample", "error"); + } + + var STROKES_FILENAME = "strokes.txt"; + + var inkCanvas; + var inkContext; + var inkPresenter; + var inkColor; + + // Undo the last stroke by removing it from the stroke container. + function undo() { + var strokeContainer = inkPresenter.strokeContainer; + var strokes = strokeContainer.getStrokes(); + + // If there are no strokes, there's nothing to undo. + if (strokes.length == 0) { + return; + } + + // Select the last stroke and delete it. + strokes[strokes.length - 1].selected = true; + strokeContainer.deleteSelected(); + } + + // Switch pen tip to eraser mode. + function eraseMode() { + inkPresenter.inputProcessingConfiguration.mode = InkInputProcessingMode.erasing; + } + + // Switch pen tip to pencil mode. + function pencilMode() { + var drawingAttributes = new Inking.InkDrawingAttributes.createForPencil(); + drawingAttributes.ignorePressure = false; + drawingAttributes.fitToCurve = true; + drawingAttributes.color = inkColor; + drawingAttributes.size = { width: 3, height: 3 }; + inkPresenter.updateDefaultDrawingAttributes(drawingAttributes); + inkPresenter.inputProcessingConfiguration.mode = InkInputProcessingMode.inking + } + + // Switch pen tip to highlighter mode. + function highlighterMode() { + var drawingAttributes = new Inking.InkDrawingAttributes(); + drawingAttributes.ignorePressure = true; + drawingAttributes.fitToCurve = true; + drawingAttributes.drawAsHighlighter = true; + drawingAttributes.penTip = Inking.PenTipShape.rectangle; + drawingAttributes.size = { width: 10, height: 10 }; + drawingAttributes.color = inkColor; + inkPresenter.updateDefaultDrawingAttributes(drawingAttributes); + inkPresenter.inputProcessingConfiguration.mode = InkInputProcessingMode.inking; + } + + // Load the strokes from the file into the stroke container. + function load() { + var strokeContainer = inkPresenter.strokeContainer; + var loadStream; + + var localAppDataFolder = Windows.Storage.ApplicationData.current.localFolder; + localAppDataFolder.getFileAsync(STROKES_FILENAME).then(function (file) { + // Open the file + return file.openAsync(Windows.Storage.FileAccessMode.read); + }).then(function (stream) { + // Load the strokes into the stroke container. + loadStream = stream; + return strokeContainer.loadAsync(stream) + }).done(function () { + // Input stream is IClosable interface and requires explicit close. + loadStream.close(); + }, + function () { + displayError("Load Failed. First save a file before loading one."); + }); + } + + // Save the strokes from the stroke container to a file. + function save() { + var strokeContainer = inkPresenter.strokeContainer; + var saveStream; + + // Create or replace existing file. + var localAppDataFolder = Windows.Storage.ApplicationData.current.localFolder; + localAppDataFolder.createFileAsync(STROKES_FILENAME, Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file) { + // Open the file + return file.openAsync(Windows.Storage.FileAccessMode.readWrite); + }).then(function (stream) { + // Save the strokes to the file. + saveStream = stream; + return strokeContainer.saveAsync(stream); + }).then(function () { + return saveStream.flushAsync(); + }).done(function () { + // Output stream is IClosable interface and requires explicit close. + saveStream.close(); + }); + } + + // Clear the stroke container and display text. + function clear() { + inkPresenter.strokeContainer.clear(); + + displayStatus(""); + displayError(""); + } + + // A button handler which fetches the ID from the button, which should be a color name. + // Set the ink presenter drawing attributes' color to that color. + function setInkColor(evt) { + switch (evt.currentTarget.id) { + case "Black": + inkColor = Windows.UI.Colors.black; + break; + case "Blue": + inkColor = Windows.UI.Colors.blue; + break; + case "Red": + inkColor = Windows.UI.Colors.red; + break; + case "Green": + inkColor = Windows.UI.Colors.green; + break; + } + + var drawingAttributes = inkPresenter.copyDefaultDrawingAttributes(); + drawingAttributes.color = inkColor; + inkPresenter.updateDefaultDrawingAttributes(drawingAttributes); + } + + function inkInitialize() { + // Utility to fetch elements by ID. + function id(elementId) { + return document.getElementById(elementId); + } + + WinJS.UI.processAll().then( + function () { + id("Black").addEventListener("click", setInkColor); + id("Blue").addEventListener("click", setInkColor); + id("Red").addEventListener("click", setInkColor); + id("Green").addEventListener("click", setInkColor); + + inkCanvas = id("InkCanvas"); + inkCanvas.setAttribute("width", inkCanvas.offsetWidth); + inkCanvas.setAttribute("height", inkCanvas.offsetHeight); + + // Get the ms-ink context + inkContext = inkCanvas.getContext("ms-ink"); + inkPresenter = inkContext.msInkPresenter; + + inkPresenter.inputDeviceTypes = + Windows.UI.Core.CoreInputDeviceTypes.mouse | + Windows.UI.Core.CoreInputDeviceTypes.pen; + + // Set the default drawing attributes. + inkColor = Windows.UI.Colors.black; + pencilMode(); + } + ).done(null, function (e) { + displayError("inkInitialize " + e.toString()); + }); + } + + // Tag the event handlers of the ToolBar so that they can be used in a declarative context. + // For security reasons WinJS.UI.processAll and WinJS.Binding.processAll (and related) functions allow only + // functions that are marked as being usable declaratively to be invoked through declarative processing. + WinJS.UI.eventHandler(undo); + WinJS.UI.eventHandler(eraseMode); + WinJS.UI.eventHandler(pencilMode); + WinJS.UI.eventHandler(highlighterMode); + WinJS.UI.eventHandler(load); + WinJS.UI.eventHandler(save); + WinJS.UI.eventHandler(clear); + WinJS.Namespace.define("Ink", { + undo: undo, + eraseMode: eraseMode, + pencilMode: pencilMode, + highlighterMode: highlighterMode, + load: load, + save: save, + clear: clear, + }); + + var page = WinJS.UI.Pages.define("/html/scenario2.html", { + ready: function (element, options) { + inkInitialize(); + } + }); +})(); \ No newline at end of file diff --git a/Samples/Ink/js/package.appxmanifest b/Samples/Ink/js/package.appxmanifest index 6694a073f7..dfa839cd47 100644 --- a/Samples/Ink/js/package.appxmanifest +++ b/Samples/Ink/js/package.appxmanifest @@ -19,7 +19,7 @@ - + diff --git a/Samples/MagneticStripeReader/cs/MagneticStripeReader.sln b/Samples/MagneticStripeReader/cs/MagneticStripeReader.sln index 520dd3b91a..b87762b24d 100644 --- a/Samples/MagneticStripeReader/cs/MagneticStripeReader.sln +++ b/Samples/MagneticStripeReader/cs/MagneticStripeReader.sln @@ -1,5 +1,4 @@  - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26228.4 @@ -39,5 +38,3 @@ Global HideSolutionNode = FALSE EndGlobalSection EndGlobal - - diff --git a/Samples/MapControl/cpp/Scenario2.xaml.cpp b/Samples/MapControl/cpp/Scenario2.xaml.cpp index 03e942b6db..33b33b3769 100644 --- a/Samples/MapControl/cpp/Scenario2.xaml.cpp +++ b/Samples/MapControl/cpp/Scenario2.xaml.cpp @@ -29,7 +29,7 @@ Scenario2::Scenario2() mapIconStreamReference = RandomAccessStreamReference::CreateFromUri(ref new Uri("ms-appx:///Assets/MapPin.png")); mapBillboardStreamReference = RandomAccessStreamReference::CreateFromUri(ref new Uri("ms-appx:///Assets/billboard.jpg")); - mapModelStreamReference = RandomAccessStreamReference::CreateFromUri(ref new Uri("ms-appx:///Assets/ConkerAfro.3mf")); + mapModelStreamReference = RandomAccessStreamReference::CreateFromUri(ref new Uri("ms-appx:///Assets/box.3mf")); } void Scenario2::MyMap_Loaded(Object^ sender, RoutedEventArgs^ e) diff --git a/Samples/MapControl/cs/MapControl.csproj b/Samples/MapControl/cs/MapControl.csproj index 1beb7c76ce..3404a8ccd9 100644 --- a/Samples/MapControl/cs/MapControl.csproj +++ b/Samples/MapControl/cs/MapControl.csproj @@ -281,7 +281,7 @@ Always
- Assets\ConkerAfro.3mf + Assets\box.3mf Always diff --git a/Samples/MessageDialog/cs/MessageDialogSample.sln b/Samples/MessageDialog/cs/MessageDialogSample.sln index 57f652c391..e35179d202 100644 --- a/Samples/MessageDialog/cs/MessageDialogSample.sln +++ b/Samples/MessageDialog/cs/MessageDialogSample.sln @@ -1,5 +1,4 @@  - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26228.4 @@ -39,5 +38,3 @@ Global HideSolutionNode = FALSE EndGlobalSection EndGlobal - - diff --git a/Samples/PdfDocument/shared/Scenario1_Render.xaml b/Samples/PdfDocument/shared/Scenario1_Render.xaml index 170aeab35a..815142e58f 100644 --- a/Samples/PdfDocument/shared/Scenario1_Render.xaml +++ b/Samples/PdfDocument/shared/Scenario1_Render.xaml @@ -32,12 +32,18 @@ - View page - + View page + + of . - + + Actual size Half size on beige background Crop to center of page @@ -45,7 +51,9 @@ +
+

Description:

+
+ Checks for availability of a verification device, such as a Microsoft Passport PIN, Windows Hello biometric, or fingerprint reader.
+

+ +

\ No newline at end of file diff --git a/Samples/UserConsentVerifier/js/html/scenario2.html b/Samples/UserConsentVerifier/js/html/scenario2.html index f8fb582436..99a2cec6b6 100644 --- a/Samples/UserConsentVerifier/js/html/scenario2.html +++ b/Samples/UserConsentVerifier/js/html/scenario2.html @@ -11,31 +11,16 @@ -
-
-

Description:

-
-

Requests fingerprint consent from the current user. Allows the calling application to specify a message to display.

-
-
-
- - - - - - - - -
- - - -
- -
+
+

Description:

+
+ Requests consent from the user using + a verification device, such as a Microsoft Passport PIN, Windows Hello biometric, or fingerprint reader.
+

+ +

\ No newline at end of file diff --git a/Samples/UserConsentVerifier/js/js/scenario1.js b/Samples/UserConsentVerifier/js/js/scenario1.js index 8ac0552fd8..242c33132f 100644 --- a/Samples/UserConsentVerifier/js/js/scenario1.js +++ b/Samples/UserConsentVerifier/js/js/scenario1.js @@ -10,25 +10,19 @@ // Check the availability of Windows Hello authentication through User Consent Verifier. function checkConsentAvailability() { - try { - - Windows.Security.Credentials.UI.UserConsentVerifier.checkAvailabilityAsync() - .then(function (consentAvailability) { - switch (consentAvailability) { - case Windows.Security.Credentials.UI.UserConsentVerifierAvailability.available: - WinJS.log && WinJS.log("User consent verification available!", "sample", "status"); - break; - case Windows.Security.Credentials.UI.UserConsentVerifierAvailability.deviceNotPresent: - WinJS.log && WinJS.log("No PIN or biometric found, please set one up.", "sample", "error"); - break; - default: - WinJS.log && WinJS.log("User consent verification is currently unavailable.", "sample", "error"); - break; - } - }); - } - catch (err) { - WinJS.log && WinJS.log("Error message: " + err.message, "sample", "error"); - } + Windows.Security.Credentials.UI.UserConsentVerifier.checkAvailabilityAsync() + .then(function (consentAvailability) { + switch (consentAvailability) { + case Windows.Security.Credentials.UI.UserConsentVerifierAvailability.available: + WinJS.log && WinJS.log("User consent verification available!", "sample", "status"); + break; + case Windows.Security.Credentials.UI.UserConsentVerifierAvailability.deviceNotPresent: + WinJS.log && WinJS.log("No PIN or biometric found, please set one up.", "sample", "error"); + break; + default: + WinJS.log && WinJS.log("User consent verification is currently unavailable.", "sample", "error"); + break; + } + }); } })(); diff --git a/Samples/UserConsentVerifier/js/js/scenario2.js b/Samples/UserConsentVerifier/js/js/scenario2.js index abddcaf4b0..378dc2332a 100644 --- a/Samples/UserConsentVerifier/js/js/scenario2.js +++ b/Samples/UserConsentVerifier/js/js/scenario2.js @@ -10,30 +10,24 @@ // Request the logged on user's consent using Windows Hello via biometric verification or a PIN. function requestConsent() { - try { - // Read the message that has to be displayed in the consent request prompt - var message = document.getElementById("Message").value; + var message = "Please confirm your identity to complete this (pretend) in-app purchase." - Windows.Security.Credentials.UI.UserConsentVerifier.requestVerificationAsync(message) - .then(function (consentResult) { - switch (consentResult) { - case Windows.Security.Credentials.UI.UserConsentVerificationResult.verified: - WinJS.log && WinJS.log("User consent verified!", "sample", "status"); - break; - case Windows.Security.Credentials.UI.UserConsentVerificationResult.deviceNotPresent: - WinJS.log && WinJS.log("No PIN or biometric found, please set one up.", "sample", "error"); - break; - case Windows.Security.Credentials.UI.UserConsentVerificationResult.canceled: - WinJS.log && WinJS.log("User consent verification canceled.", "sample", "error"); - break; - default: - WinJS.log && WinJS.log("User consent verification is currently unavailable.", "sample", "error"); - break; - } - }); - } - catch (err) { - WinJS.log && WinJS.log("Error message: " + err.message, "sample", "error"); - } + Windows.Security.Credentials.UI.UserConsentVerifier.requestVerificationAsync(message) + .then(function (consentResult) { + switch (consentResult) { + case Windows.Security.Credentials.UI.UserConsentVerificationResult.verified: + WinJS.log && WinJS.log("Pretend in-app purchase was successful.", "sample", "status"); + break; + case Windows.Security.Credentials.UI.UserConsentVerificationResult.deviceNotPresent: + WinJS.log && WinJS.log("No PIN or biometric found, please set one up.", "sample", "error"); + break; + case Windows.Security.Credentials.UI.UserConsentVerificationResult.canceled: + WinJS.log && WinJS.log("User consent verification canceled.", "sample", "error"); + break; + default: + WinJS.log && WinJS.log("User consent verification is currently unavailable.", "sample", "error"); + break; + } + }); } })(); diff --git a/Samples/UserConsentVerifier/shared/Scenario1_CheckConsentAvailability.xaml b/Samples/UserConsentVerifier/shared/Scenario1_CheckConsentAvailability.xaml new file mode 100644 index 0000000000..1accc64c64 --- /dev/null +++ b/Samples/UserConsentVerifier/shared/Scenario1_CheckConsentAvailability.xaml @@ -0,0 +1,31 @@ + + + + + + + + Checks for availability of a verification device, such as a Microsoft Passport PIN, Windows Hello biometric, or fingerprint reader. + + + + +

+

+ +
+ +

+

+ +

+

+

+ + + \ No newline at end of file diff --git a/Samples/WebSocket/js/js/sample-configuration.js b/Samples/WebSocket/js/js/sample-configuration.js index 9a246b6bfa..0a82e79847 100644 --- a/Samples/WebSocket/js/js/sample-configuration.js +++ b/Samples/WebSocket/js/js/sample-configuration.js @@ -20,7 +20,8 @@ var scenarios = [ { url: "/html/scenario1-utf8.html", title: "UTF-8 text messages" }, { url: "/html/scenario2-binary.html", title: "Binary data stream" }, - { url: "/html/scenario3-clientCertificate.html", title: "Client certificate" } + { url: "/html/scenario3-clientCertificate.html", title: "Client certificate" }, + { url: "/html/scenario4-partialReadWrite.html", title: "Partial and Complete Messages" } ]; // Look up the name for an enumeration member. diff --git a/Samples/WebSocket/js/js/scenario1-utf8.js b/Samples/WebSocket/js/js/scenario1-utf8.js index 6bfd8f7fca..27493299fd 100644 --- a/Samples/WebSocket/js/js/scenario1-utf8.js +++ b/Samples/WebSocket/js/js/scenario1-utf8.js @@ -15,6 +15,7 @@ var MessageWebSocket = Windows.Networking.Sockets.MessageWebSocket; var SocketMessageType = Windows.Networking.Sockets.SocketMessageType; var UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding; + var ChainValidationResult = Windows.Security.Cryptography.Certificates.ChainValidationResult; // Local variables var messageWebSocket; @@ -77,11 +78,10 @@ // input). The URI is validated by calling validateAndCreateUri() that will return null // for strings that are not valid WebSocket URIs. // Note that when enabling the text box users may provide URIs to machines on the local network - // or internet. In these cases the app requires the "Home or Work Networking" or + // or internet. In these cases the app requires the "Private Networks (Client and Server)" or // "Internet (Client)" capability respectively. var server = SdkSample.validateAndCreateUri(serverAddressField.value); - if (!server) - { + if (!server) { return WinJS.Promise.wrap(); } diff --git a/Samples/WebSocket/js/js/scenario4-partialReadWrite.js b/Samples/WebSocket/js/js/scenario4-partialReadWrite.js new file mode 100644 index 0000000000..b7662900b2 --- /dev/null +++ b/Samples/WebSocket/js/js/scenario4-partialReadWrite.js @@ -0,0 +1,221 @@ +//********************************************************* +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License (MIT). +// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF +// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY +// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR +// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. +// +//********************************************************* + +(function () { + "use strict"; + + var MessageWebSocket = Windows.Networking.Sockets.MessageWebSocket; + var SocketMessageType = Windows.Networking.Sockets.SocketMessageType; + var MessageWebSocketReceiveMode = Windows.Networking.Sockets.MessageWebSocketReceiveMode; + var UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding; + var ChainValidationResult = Windows.Security.Cryptography.Certificates.ChainValidationResult; + + // Local variables + var messageWebSocket; + var busy; + + // DOM elements + var serverAddressField; + var connectButton; + var disconnectButton; + var inputField; + var endOfMessageCheckBox; + var sendButton; + var outputField; + + var page = WinJS.UI.Pages.define("/html/scenario4-partialReadWrite.html", { + ready: function (element, options) { + serverAddressField = document.getElementById("serverAddressField"); + connectButton = document.getElementById("connectButton"); + disconnectButton = document.getElementById("disconnectButton"); + inputField = document.getElementById("inputField"); + endOfMessageCheckBox = document.getElementById("endOfMessageCheckBox"); + sendButton = document.getElementById("sendButton"); + outputField = document.getElementById("outputField"); + + connectButton.addEventListener("click", onConnect, false); + disconnectButton.addEventListener("click", onDisconnect, false); + sendButton.addEventListener("click", onSend, false); + + updateVisualState(); + }, + unload: function (eventObject) { + closeSocket(); + } + }); + + function updateVisualState() { + serverAddressField.disabled = busy || messageWebSocket; + connectButton.disabled = busy || messageWebSocket; + disconnectButton.disabled = busy || !messageWebSocket; + inputField.disabled = busy || !messageWebSocket + sendButton.disabled = busy || !messageWebSocket + } + + function setBusy(value) { + busy = value; + updateVisualState(); + } + + function onConnect() { + setBusy(true); + connectAsync().done(function () { + setBusy(false); + }); + } + + function connectAsync() { + // Validating the URI is required since it was received from an untrusted source (user + // input). The URI is validated by calling validateAndCreateUri() that will return null + // for strings that are not valid WebSocket URIs. + // Note that when enabling the text box users may provide URIs to machines on the local network + // or internet. In these cases the app requires the "Private Networks (Client and Server)" or + // "Internet (Client)" capability respectively. + var server = SdkSample.validateAndCreateUri(serverAddressField.value); + if (!server) { + return WinJS.Promise.wrap(); + } + + // Set up the socket data format and callbacks + messageWebSocket = new MessageWebSocket(); + messageWebSocket.control.messageType = SocketMessageType.utf8; + + // To support receiving event notifications for partial messages, you must set this receive mode. + // If you do not set this mode, you will only receive notifications for complete messages; which is + // the default behavior. Setting to Partial allows us to process partial data as soon as it arrives, + // as opposed to waiting until the EndOfMessage to process the entire data. + messageWebSocket.control.receiveMode = MessageWebSocketReceiveMode.partialMessage; + + messageWebSocket.addEventListener("messagereceived", onMessageReceived); + messageWebSocket.addEventListener("closed", onClosed); + + if (server.schemeName == "wss") + { + // WARNING: Only test applications should ignore SSL errors. + // In real applications, ignoring server certificate errors can lead to Man-In-The-Middle + // attacks. (Although the connection is secure, the server is not authenticated.) + // Note that not all certificate validation errors can be ignored. + // In this case, we are ignoring these errors since the certificate assigned to the localhost + // URI is self-signed and has subject name = fabrikam.com + messageWebSocket.control.ignorableServerCertificateErrors.push( + ChainValidationResult.untrusted, + ChainValidationResult.invalidName); + } + + appendOutputLine("Connecting to: " + server.absoluteUri); + + return messageWebSocket.connectAsync(server).then(function () { + WinJS.log && WinJS.log("Connected", "sample", "status"); + }, function (error) { + messageWebSocket.close(); + messageWebSocket = null; + + appendOutputLine(SdkSample.buildWebSocketError(error)); + appendOutputLine(error.message); + }); + } + + function onSend() { + setBusy(true); + sendAsync().done(function () { + setBusy(false); + }); + } + + function sendAsync() { + var message = inputField.value; + if (message === "") { + WinJS.log && WinJS.log("Please specify text to send", "sample", "error"); + return WinJS.Promise.wrap(); + } + + // Buffer any data we want to send. + var messageWriter = new Windows.Storage.Streams.DataWriter(); + messageWriter.writeString(message); + var buffer = messageWriter.detachBuffer(); + + // Send the data as one complete message. + var asyncTask = null; + + if (endOfMessageCheckBox.checked == true) { + appendOutputLine("Sending end of message: " + message); + asyncTask = messageWebSocket.sendFinalFrameAsync(buffer); + } + else { + appendOutputLine("Sending partial message: " + message); + asyncTask = messageWebSocket.sendNonfinalFrameAsync(buffer); + } + + return asyncTask.then(function() { + WinJS.log && WinJS.log("Send Complete", "sample", "status"); + }, function(error) { + appendOutputLine(SdkSample.buildWebSocketError(error)); + appendOutputLine(error.message); + }); + } + + function onMessageReceived(args) { + + var partialOrCompleted = "Partial"; + + if (args.isMessageComplete) { + partialOrCompleted = "Complete"; + } + + appendOutputLine(partialOrCompleted + " message received; Type: " + SdkSample.lookupEnumName(SocketMessageType, args.messageType)); + + // The incoming message is already buffered. + var reader = args.getDataReader(); + reader.unicodeEncoding = UnicodeEncoding.utf8; + + try { + // Note that it may be possible for partial UTF8 messages to be split between a character if it + // extends multiple bytes. We avoid this by using only ASCII characters in this example. + // Should your application use multi-byte characters, you will need to do checks for broken characters. + appendOutputLine(reader.readString(reader.unconsumedBufferLength)); + } catch (error) { + appendOutputLine(SdkSample.buildWebSocketError(error)); + appendOutputLine(error.message); + } + } + + function onDisconnect() { + setBusy(true); + WinJS.log && WinJS.log("Closing", "sample", "status"); + closeSocket(); + setBusy(false); + } + + function onClosed(e) { + appendOutputLine("Closed; Code: " + e.code + " Reason: " + e.reason); + if (messageWebSocket) { + closeSocket(); + updateVisualState(); + } + } + + function closeSocket() { + if (messageWebSocket) { + try { + messageWebSocket.close(1000, "Closed due to user request."); + } catch (error) { + appendOutputLine(SdkSample.buildWebSocketError(error)); + appendOutputLine(error.message); + } + messageWebSocket = null; + } + } + + function appendOutputLine(text) { + outputField.innerText += "\r\n" + text; + } + +})(); diff --git a/Samples/WebSocket/server/setupserver.ps1 b/Samples/WebSocket/server/setupserver.ps1 index 5b0a8a67ba..5809f5f4a4 100644 --- a/Samples/WebSocket/server/setupserver.ps1 +++ b/Samples/WebSocket/server/setupserver.ps1 @@ -13,7 +13,6 @@ $scriptPath = $(Split-Path $MyInvocation.MyCommand.Path) $iisAppName = "WebSocketSample" $iisAppPath = "$env:systemdrive\inetpub\wwwroot\WebSocketSample" $websitePath = "$scriptPath\website" -$webpageFileName = "echowebsocket.ashx" $clientCertWebpageFileName = "EchoWebSocketWithClientAuthentication.ashx" $wsfirewallRuleName = "WebSocketSample - HTTP 80" $wssFirewallRuleName = "WebSocketSample - HTTPS 443" @@ -54,8 +53,8 @@ if ($featuresToEnable.Count -gt 0) if (-not (Test-Path $iisAppPath)) { mkdir $iisAppPath > $null - Copy-Item $websitePath\$webpageFileName $iisAppPath - Copy-Item $websitePath\$webpageFileName $iisAppPath\$clientCertWebpageFileName + Copy-Item $websitePath\* $iisAppPath -r + Copy-Item $websitePath\echowebsocket.ashx $iisAppPath\$clientCertWebpageFileName } # Add web application. diff --git a/Samples/WebSocket/server/website/partialMessages.ashx b/Samples/WebSocket/server/website/partialMessages.ashx new file mode 100644 index 0000000000..ab433aa22a --- /dev/null +++ b/Samples/WebSocket/server/website/partialMessages.ashx @@ -0,0 +1,94 @@ + + +<%@ WebHandler Language="C#" Class="EchoWebSocket" %> + +// This define is needed to enable System.Diagnostics.Trace methods. For more information go to: +// http://msdn.microsoft.com/en-us/library/system.diagnostics.trace(v=vs.110).aspx +#define TRACE + +using System; +using System.Net.WebSockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Web; +using System.Web.WebSockets; + +public class EchoWebSocket : IHttpHandler { + private const int MaxBufferSize = 64 * 1024; + + public void ProcessRequest (HttpContext context) + { + try + { + context.AcceptWebSocketRequest(async wsContext => + { + try + { + byte[] receiveBuffer = new byte[MaxBufferSize]; + ArraySegment buffer = new ArraySegment(receiveBuffer); + WebSocket socket = wsContext.WebSocket; + string userString; + + if (socket.State == WebSocketState.Open) + { + // Announcement when connected + var announceString = "EchoWebSocket Connected at: " + DateTime.Now.ToString(); + ArraySegment outputBuffer2 = new ArraySegment(Encoding.UTF8.GetBytes(announceString)); + await socket.SendAsync(outputBuffer2, WebSocketMessageType.Text, true, CancellationToken.None); + } + + // Stay in loop while websocket is open + while (socket.State == WebSocketState.Open) + { + WebSocketReceiveResult receiveResult = await socket.ReceiveAsync(buffer, CancellationToken.None); + + if (receiveResult.MessageType == WebSocketMessageType.Close) + { + // Echo back code and reason strings + await socket.CloseAsync( + receiveResult.CloseStatus.GetValueOrDefault(), + receiveResult.CloseStatusDescription, + CancellationToken.None); + return; + } + + // For the sake of the example, we're assuming we're only receiving UTF8 strings + string cmdString = Encoding.UTF8.GetString(buffer.Array, 0, receiveResult.Count); + userString = "You said: \"" + cmdString + "\"\r\n"; + + ArraySegment outputBuffer = new ArraySegment(Encoding.UTF8.GetBytes(userString)); + await socket.SendAsync(outputBuffer, WebSocketMessageType.Text, receiveResult.EndOfMessage, CancellationToken.None); + } + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex); + } + }); + } + catch (Exception ex) + { + System.Diagnostics.Trace.WriteLine(ex); + context.Response.StatusCode = 500; + context.Response.StatusDescription = ex.Message; + context.Response.End(); + } + } + + public bool IsReusable + { + get + { + return false; + } + } +} diff --git a/Samples/WebSocket/shared/Scenario4_PartialReadWrite.xaml b/Samples/WebSocket/shared/Scenario4_PartialReadWrite.xaml new file mode 100644 index 0000000000..acc5ec6a61 --- /dev/null +++ b/Samples/WebSocket/shared/Scenario4_PartialReadWrite.xaml @@ -0,0 +1,73 @@ + + + + + + + + + + + + This scenario uses a MessageWebSocket to send partial or complete messages. + The sample server supports both plaintext WebSocket (ws://) and secure WebSocket (wss://) server endpoints. + + + + + - + --> \ No newline at end of file diff --git a/Samples/XamlUIBasics/cs/AppUIBasics/PageHeader.xaml.cs b/Samples/XamlUIBasics/cs/AppUIBasics/PageHeader.xaml.cs index 45088b2776..eb0cab1478 100644 --- a/Samples/XamlUIBasics/cs/AppUIBasics/PageHeader.xaml.cs +++ b/Samples/XamlUIBasics/cs/AppUIBasics/PageHeader.xaml.cs @@ -71,7 +71,7 @@ public void UpdateBackground(bool isFilteredPage) VisualStateManager.GoToState(this, isFilteredPage ? "FilteredPage" : "NonFilteredPage", false); } - private void OnControlsSearchBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) + /*private void OnControlsSearchBoxTextChanged(AutoSuggestBox sender, AutoSuggestBoxTextChangedEventArgs args) { if (args.Reason == AutoSuggestionBoxTextChangeReason.UserInput) { @@ -96,9 +96,9 @@ private void OnControlsSearchBoxTextChanged(AutoSuggestBox sender, AutoSuggestBo controlsSearchBox.ItemsSource = new string[] { "No results found" }; } } - } + }*/ - private void OnControlsSearchBoxQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) + /*private void OnControlsSearchBoxQuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args) { if (args.ChosenSuggestion != null && args.ChosenSuggestion is ControlInfoDataItem) { @@ -109,9 +109,9 @@ private void OnControlsSearchBoxQuerySubmitted(AutoSuggestBox sender, AutoSugges { NavigationRootPage.RootFrame.Navigate(typeof(SearchResultsPage), args.QueryText); } - } + }*/ - private void OnSearchButtonClick(object sender, RoutedEventArgs e) + /*private void OnSearchButtonClick(object sender, RoutedEventArgs e) { controlsSearchBox.Visibility = Visibility.Visible; bool isFocused = controlsSearchBox.Focus(FocusState.Programmatic); @@ -122,9 +122,9 @@ private void OnSearchButtonClick(object sender, RoutedEventArgs e) } searchButton.Visibility = Visibility.Collapsed; commandBarBorder.Visibility = Visibility.Collapsed; - } + }*/ - private void OnControlsSearchBoxLostFocus(object sender, RoutedEventArgs e) + /*private void OnControlsSearchBoxLostFocus(object sender, RoutedEventArgs e) { if (Window.Current.Bounds.Width <= 640) { @@ -132,9 +132,9 @@ private void OnControlsSearchBoxLostFocus(object sender, RoutedEventArgs e) commandBarBorder.Visibility = Visibility.Visible; searchButton.Visibility = Visibility.Visible; } - } + }*/ - private void OnThemeButtonKeyDown(object sender, KeyRoutedEventArgs e) + /*private void OnThemeButtonKeyDown(object sender, KeyRoutedEventArgs e) { if (e.Key == VirtualKey.Right) { @@ -144,16 +144,16 @@ private void OnThemeButtonKeyDown(object sender, KeyRoutedEventArgs e) controlsSearchBox.Focus(FocusState.Programmatic); } } - } + }*/ private void OnThemeButtonClick(object sender, RoutedEventArgs e) { ToggleThemeAction?.Invoke(); } - private void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) + /*private void KeyboardAccelerator_Invoked(KeyboardAccelerator sender, KeyboardAcceleratorInvokedEventArgs args) { controlsSearchBox.Focus(FocusState.Keyboard); - } + }*/ } } \ No newline at end of file diff --git a/SharedContent/Templates/UWPSDKSampleCPP/Package.appxmanifest b/SharedContent/Templates/UWPSDKSampleCPP/Package.appxmanifest index daf707f50f..4148ab3f57 100644 --- a/SharedContent/Templates/UWPSDKSampleCPP/Package.appxmanifest +++ b/SharedContent/Templates/UWPSDKSampleCPP/Package.appxmanifest @@ -20,7 +20,7 @@ - + diff --git a/SharedContent/Templates/UWPSDKSampleCPP/UWPSDKSampleCPP.vcxproj b/SharedContent/Templates/UWPSDKSampleCPP/UWPSDKSampleCPP.vcxproj index ac73dda5f1..39864f0d7f 100644 --- a/SharedContent/Templates/UWPSDKSampleCPP/UWPSDKSampleCPP.vcxproj +++ b/SharedContent/Templates/UWPSDKSampleCPP/UWPSDKSampleCPP.vcxproj @@ -8,8 +8,8 @@ true Windows Store 10.0 - 10.0.15063.0 - 10.0.15063.0 + 10.0.16299.0 + 10.0.16299.0 diff --git a/SharedContent/Templates/UWPSDKSampleCS/Package.appxmanifest b/SharedContent/Templates/UWPSDKSampleCS/Package.appxmanifest index 9bb134b0bb..fe24dcac84 100644 --- a/SharedContent/Templates/UWPSDKSampleCS/Package.appxmanifest +++ b/SharedContent/Templates/UWPSDKSampleCS/Package.appxmanifest @@ -20,7 +20,7 @@ - + diff --git a/SharedContent/Templates/UWPSDKSampleCS/UWPSDKSampleCS.csproj b/SharedContent/Templates/UWPSDKSampleCS/UWPSDKSampleCS.csproj index 7d3431403d..5bd4ce4e81 100644 --- a/SharedContent/Templates/UWPSDKSampleCS/UWPSDKSampleCS.csproj +++ b/SharedContent/Templates/UWPSDKSampleCS/UWPSDKSampleCS.csproj @@ -11,8 +11,8 @@ $safeprojectname$ en-US UAP - 10.0.15063.0 - 10.0.15063.0 + 10.0.16299.0 + 10.0.16299.0 15 true 512 diff --git a/SharedContent/Templates/UWPSDKSampleJS/App.jsproj b/SharedContent/Templates/UWPSDKSampleJS/App.jsproj index 58f2fd5c05..92364f9bdb 100755 --- a/SharedContent/Templates/UWPSDKSampleJS/App.jsproj +++ b/SharedContent/Templates/UWPSDKSampleJS/App.jsproj @@ -45,8 +45,8 @@ UAP - 10.0.15063.0 - 10.0.15063.0 + 10.0.16299.0 + 10.0.16299.0 $(VersionNumberMajor).$(VersionNumberMinor) en-US diff --git a/SharedContent/Templates/UWPSDKSampleJS/package.appxmanifest b/SharedContent/Templates/UWPSDKSampleJS/package.appxmanifest index 4c24ebbd29..6bf41bd24b 100755 --- a/SharedContent/Templates/UWPSDKSampleJS/package.appxmanifest +++ b/SharedContent/Templates/UWPSDKSampleJS/package.appxmanifest @@ -19,7 +19,7 @@ - + diff --git a/SharedContent/cpp/MainPage.xaml b/SharedContent/cpp/MainPage.xaml index 5179f520f9..d5c559ddc5 100644 --- a/SharedContent/cpp/MainPage.xaml +++ b/SharedContent/cpp/MainPage.xaml @@ -29,7 +29,8 @@ - + @@ -58,7 +59,8 @@ - + @@ -73,7 +75,10 @@ - + +
diff --git a/SharedContent/cpp/MainPage.xaml.cpp b/SharedContent/cpp/MainPage.xaml.cpp index 24c12f685f..842a5a9e8b 100644 --- a/SharedContent/cpp/MainPage.xaml.cpp +++ b/SharedContent/cpp/MainPage.xaml.cpp @@ -18,6 +18,7 @@ using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::UI::Core; using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Automation::Peers; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Xaml::Controls::Primitives; using namespace Windows::UI::Xaml::Data; @@ -140,6 +141,13 @@ void MainPage::UpdateStatus(String^ strMessage, NotifyType type) StatusBorder->Visibility = Windows::UI::Xaml::Visibility::Collapsed; StatusPanel->Visibility = Windows::UI::Xaml::Visibility::Collapsed; } + + // Raise an event if necessary to enable a screen reader to announce the status update. + auto peer = dynamic_cast(FrameworkElementAutomationPeer::FromElement(StatusBlock)); + if (peer != nullptr) + { + peer->RaiseAutomationEvent(AutomationEvents::LiveRegionChanged); + } } void MainPage::Footer_Click(Object^ sender, RoutedEventArgs^ e) diff --git a/SharedContent/cs/MainPage.xaml b/SharedContent/cs/MainPage.xaml index 66667d098c..2af66d6acc 100644 --- a/SharedContent/cs/MainPage.xaml +++ b/SharedContent/cs/MainPage.xaml @@ -33,7 +33,8 @@ - + @@ -62,7 +63,8 @@ - + @@ -78,7 +80,10 @@ - + + diff --git a/SharedContent/cs/MainPage.xaml.cs b/SharedContent/cs/MainPage.xaml.cs index 6fbd3a3285..81d5bf1273 100644 --- a/SharedContent/cs/MainPage.xaml.cs +++ b/SharedContent/cs/MainPage.xaml.cs @@ -13,6 +13,7 @@ using System.Collections.Generic; using Windows.UI.Core; using Windows.UI.Xaml; +using Windows.UI.Xaml.Automation.Peers; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Media; @@ -127,7 +128,14 @@ private void UpdateStatus(string strMessage, NotifyType type) StatusBorder.Visibility = Visibility.Collapsed; StatusPanel.Visibility = Visibility.Collapsed; } - } + + // Raise an event if necessary to enable a screen reader to announce the status update. + var peer = FrameworkElementAutomationPeer.FromElement(StatusBlock); + if (peer != null) + { + peer.RaiseAutomationEvent(AutomationEvents.LiveRegionChanged); + } + } async void Footer_Click(object sender, RoutedEventArgs e) {