diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.cpp.tt b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.cpp.tt new file mode 100644 index 0000000..b510317 --- /dev/null +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.cpp.tt @@ -0,0 +1,3 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<# string classDefFile = "..\\winsdkfb.Shared\\FBVideo.xml"; #> +<#@ include file="FBGraphObjectImplementation.ttinclude" #> diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.h.tt b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.h.tt new file mode 100644 index 0000000..d9111c2 --- /dev/null +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.h.tt @@ -0,0 +1,3 @@ +<#@ template debug="false" hostspecific="false" language="C#" #> +<# string classDefFile = "..\\winsdkfb.Shared\\FBVideo.xml"; #> +<#@ include file="FBGraphObjectHeader.ttinclude" #> diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.xml b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.xml new file mode 100644 index 0000000..f8cd982 --- /dev/null +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideo.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoContentStream.cpp b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoContentStream.cpp new file mode 100644 index 0000000..000be57 --- /dev/null +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoContentStream.cpp @@ -0,0 +1,126 @@ +//****************************************************************************** +// +// Copyright (c) 2016 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#include "pch.h" +#include "FBVideoContentStream.h" + +using namespace concurrency; +using namespace Windows::Storage::Streams; +using namespace Windows::Foundation; + +FBVideoContentStream::FBVideoContentStream( + IRandomAccessStream^ stream, + Platform::String^ contentType + ) : + _contentType{contentType}, + _stream{stream} +{ +} + +IAsyncOperationWithProgress^ FBVideoContentStream::ReadAsync( + Windows::Storage::Streams::IBuffer^ buffer, + unsigned int count, + Windows::Storage::Streams::InputStreamOptions options + ) +{ + return _stream->ReadAsync(buffer, count, options); +} + +IAsyncOperationWithProgress^ FBVideoContentStream::WriteAsync(Windows::Storage::Streams::IBuffer^ buffer) +{ + return _stream->WriteAsync(buffer); +} + +IAsyncOperation^ FBVideoContentStream::FlushAsync() +{ + return _stream->FlushAsync(); +} + +bool FBVideoContentStream::CanRead::get() +{ + return _stream->CanRead; +} + +bool FBVideoContentStream::CanWrite::get() +{ + return _stream->CanWrite; +} + +unsigned long long FBVideoContentStream::Position::get() +{ + return _stream->Position; +} + +unsigned long long FBVideoContentStream::Size::get() +{ + return _stream->Size; +} + +void FBVideoContentStream::Size::set(unsigned long long value) +{ + _stream->Size = value; +} + +IInputStream^ FBVideoContentStream::GetInputStreamAt(unsigned long long position) +{ + return _stream->GetInputStreamAt(position); +} + +IOutputStream^ FBVideoContentStream::GetOutputStreamAt(unsigned long long position) +{ + return _stream->GetOutputStreamAt(position); +} + +void FBVideoContentStream::Seek(unsigned long long position) +{ + _stream->Seek(position); +} + +IRandomAccessStream^ FBVideoContentStream::CloneStream() +{ + return ref new FBVideoContentStream(_stream->CloneStream(), _contentType); +} + +Platform::String^ FBVideoContentStream::ContentType::get() +{ + return _contentType; +} + +FBVideoContentStream::~FBVideoContentStream() +{ +} + +IAsyncOperation^ FBVideoContentStream::TruncateCloneStreamAsync(unsigned long long position, unsigned int size) +{ + unsigned long long oldPosition = _stream->Position; + _stream->Seek(position); + IBuffer^ buffer = ref new Buffer(size); + + task workTask = create_task(_stream->ReadAsync(buffer, size, InputStreamOptions::None)).then([=](IBuffer^ filledBuffer) + { + _stream->Seek(oldPosition); + InMemoryRandomAccessStream^ memoryStream = ref new InMemoryRandomAccessStream(); + return create_task(memoryStream->WriteAsync(filledBuffer)).then([=](unsigned int count) + { + memoryStream->Seek(0); + return ref new FBVideoContentStream(memoryStream, _contentType); + }); + }); + return create_async([=]() + { + return workTask; + }); +} diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoContentStream.h b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoContentStream.h new file mode 100644 index 0000000..0d21c82 --- /dev/null +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoContentStream.h @@ -0,0 +1,101 @@ +//****************************************************************************** +// +// Copyright (c) 2016 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#pragma once + +using namespace Windows::Storage::Streams; + +ref class FBVideoContentStream sealed : IRandomAccessStreamWithContentType +{ +public: + + FBVideoContentStream( + IRandomAccessStream^ stream, + Platform::String^ contentType + ); + + // Inherited via IRandomAccessStream + virtual Windows::Foundation::IAsyncOperationWithProgress^ + ReadAsync( + Windows::Storage::Streams::IBuffer^buffer, + unsigned int count, + Windows::Storage::Streams::InputStreamOptions options + ); + + virtual Windows::Foundation::IAsyncOperationWithProgress^ + WriteAsync( + Windows::Storage::Streams::IBuffer^buffer + ); + + virtual Windows::Foundation::IAsyncOperation^ FlushAsync(); + + virtual property bool CanRead + { + bool get(); + } + + virtual property bool CanWrite + { + bool get(); + } + + virtual property unsigned long long Position + { + unsigned long long get(); + } + + virtual property unsigned long long Size + { + unsigned long long get(); + void set(unsigned long long); + } + + virtual Windows::Storage::Streams::IInputStream^ GetInputStreamAt( + unsigned long long position + ); + + virtual Windows::Storage::Streams::IOutputStream^ GetOutputStreamAt( + unsigned long long position + ); + + virtual void Seek( + unsigned long long position + ); + + virtual Windows::Storage::Streams::IRandomAccessStream^ CloneStream(); + + // Inherited via IContentTypeProvider + virtual property Platform::String^ ContentType + { + Platform::String^ get(); + } + + virtual ~FBVideoContentStream(); + + /** + * Clones a portion of the stream into another object. + * @param position The position in the stream to start the clone at + * @param size The amount of bytes to clone + * @return The cloned, truncated FBVideoContentStream + */ + Windows::Foundation::IAsyncOperation^ TruncateCloneStreamAsync( + unsigned long long position, + unsigned int size + ); +private: + Platform::String^ _contentType; + IRandomAccessStream^ _stream; +}; diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoUploader.cpp b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoUploader.cpp new file mode 100644 index 0000000..2591240 --- /dev/null +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoUploader.cpp @@ -0,0 +1,238 @@ +//****************************************************************************** +// +// Copyright (c) 2016 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#include + +#include "pch.h" +#include "FBVideoUploader.h" +#include "FacebookClient.h" +#include "FacebookSession.h" +#include "FacebookMediaObject.h" +#include "FacebookMediaStream.h" +#include "FBSingleValue.h" +#include "FBVideo.h" + +using namespace concurrency; +using namespace winsdkfb; +using namespace winsdkfb::Graph; +using namespace Platform; +using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; +using namespace Windows::Storage; +using namespace Windows::Storage::FileProperties; +using namespace Windows::Storage::Streams; +using namespace Windows::Security::Cryptography; +using namespace Windows::Security::Cryptography::DataProtection; + +// Facebook Video upload doesn't specify whether their video threshold for small vs. large upload +// is 1 gigabyte or 1 gibibyte. We have put down the 1 gigabyte just to be safe. +#define VIDEO_THRESHOLD_SIZE 1000000000L + +namespace winsdkfb +{ + + IAsyncOperation^ FBVideoUploader::UploadVideoAsync(StorageFile^ videoFile) + { + task workTask = create_task(videoFile->GetBasicPropertiesAsync()).then([=](BasicProperties^ properties) + { + unsigned long long fileSize = properties->Size; + // Facebook expects a file size of < 1 GB for small video upload and between 1 GB and 1.5 GB for large + // video upload. We are only validating the file size portion before upload. + if (fileSize < VIDEO_THRESHOLD_SIZE) + { + return UploadVideoAsync(videoFile, VideoUploadBehavior::SingleRequest); + } + else + { + return UploadVideoAsync(videoFile, VideoUploadBehavior::MultiRequest); + } + }); + return create_async([=]() + { + return workTask; + }); + } + + IAsyncOperation^ FBVideoUploader::UploadVideoAsync(StorageFile^ videoFile, VideoUploadBehavior uploadBehavior) + { + task workTask = create_task(videoFile->GetBasicPropertiesAsync()).then([=](BasicProperties^ properties) + { + unsigned long long fileSize = properties->Size; + FBSession^ sess = FBSession::ActiveSession; + if (sess->LoggedIn) + { + if (uploadBehavior == VideoUploadBehavior::SingleRequest) + { + return UploadSmallVideo(videoFile); + } + else + { + return UploadLargeVideo(videoFile, fileSize); + } + } + return create_task([=]() -> FBResult^ + { + return ref new FBResult(ref new FBError(0, L"Not logged in", L"Need to be logged in to upload a video")); + }); + }); + + return create_async([=]() + { + return workTask; + }); + } + + task FBVideoUploader::UploadSmallVideo(StorageFile^ videoFile) + { + return create_task(videoFile->OpenReadAsync()).then([=](IRandomAccessStreamWithContentType^ fileStream) + { + FBMediaStream^ videoMediaStream = ref new FBMediaStream(videoFile->Name, fileStream); + FBSession^ sess = FBSession::ActiveSession; + PropertySet^ parameters = ref new PropertySet(); + parameters->Insert(L"title", videoFile->Name); + parameters->Insert(L"source", videoMediaStream); + + String^ path = sess->User->Id + L"/videos"; + FBJsonClassFactory^ factory = ref new FBJsonClassFactory(FBVideo::FromJson); + + FBSingleValue^ request = ref new FBSingleValue(path, parameters, factory); + return request->PostAsync(); + }); + } + + task FBVideoUploader::UploadLargeVideo(StorageFile^ videoFile, unsigned long long fileSize) + { + return StartRequest(fileSize).then([=](FBResult^ result) + { + if (!result->Succeeded) + { + return create_task([=]() + { + return result; + }); + } + else + { + FBVideo^ video = static_cast(result->Object); + return create_task(videoFile->OpenAsync(FileAccessMode::Read)).then([=](IRandomAccessStream^ stream) + { + FBVideoContentStream^ contentStream = ref new FBVideoContentStream(stream, videoFile->ContentType); + return std::make_pair(video, contentStream); + }).then([=](std::pair videoData) + { + FBVideo^ video = videoData.first; + FBVideoContentStream^ stream = videoData.second; + return TransferRequest(stream, video, videoFile->Name); + }).then([=](FBResult^ result) { + if (result->Succeeded) + { + FBVideo^ video = static_cast(result->Object); + return FinishRequest(video->UploadSessionId); + } + else + { + return create_task([=]() + { + return result; + }); + } + }); + } + }); + } + + task FBVideoUploader::StartRequest(unsigned long long fileSize) + { + PropertySet^ parameters = ref new PropertySet(); + parameters->Insert(L"upload_phase", L"start"); + parameters->Insert(L"file_size", fileSize.ToString()); + + FBSession^ sess = FBSession::ActiveSession; + String^ path = sess->User->Id + L"/videos"; + FBJsonClassFactory^ factory = ref new FBJsonClassFactory(FBVideo::FromJson); + FBSingleValue^ request = ref new FBSingleValue(path, parameters, factory); + return create_task(request->PostAsync()); + } + + task FBVideoUploader::TransferRequest( + FBVideoContentStream^ stream, + FBVideo^ video, + String^ title + ) + { + return create_task([=]() + { + FBJsonClassFactory^ factory = ref new FBJsonClassFactory(FBVideo::FromJson); + int start = _wtoi(video->StartOffset->Data()); + int count = _wtoi(video->EndOffset->Data()) - start; + FBResult^ result; + while (count > 0) + { + task workTask = create_task(stream->TruncateCloneStreamAsync(start, count)).then([=](FBVideoContentStream^ truncatedStream) + { + FBMediaStream^ chunkMediaStream = ref new FBMediaStream(title, truncatedStream); + PropertySet^ parameters = ref new PropertySet(); + parameters->Insert(L"upload_phase", L"transfer"); + parameters->Insert(L"start_offset", start.ToString()); + parameters->Insert(L"upload_session_id", video->UploadSessionId); + parameters->Insert(L"video_file_chunk", chunkMediaStream); + + FBSession^ sess = FBSession::ActiveSession; + String^ path = sess->User->Id + L"/videos"; + FBSingleValue^ request = ref new FBSingleValue(path, parameters, factory); + return request->PostAsync(); + }); + workTask.wait(); + result = workTask.get(); + if (result->Succeeded) + { + FBVideo^ v = static_cast(result->Object); + // need to set UploadSessionId so that the next stage after + // the transfer can refer to it + v->UploadSessionId = video->UploadSessionId; + start = _wtoi(v->StartOffset->Data()); + count = _wtoi(v->EndOffset->Data()) - start; + } + else + { + break; + } + } + if (count < 0) + { + result = ref new FBResult( + ref new FBError( + 0, + L"Video upload error", + "transfer step reached an invalid byte range")); + } + return result; + }); + } + + task FBVideoUploader::FinishRequest(Platform::String^ uploadSessionId) + { + PropertySet^ parameters = ref new PropertySet(); + parameters->Insert(L"upload_phase", L"finish"); + parameters->Insert(L"upload_session_id", uploadSessionId); + + FBSession^ sess = FBSession::ActiveSession; + String^ path = sess->User->Id + L"/videos"; + FBJsonClassFactory^ factory = ref new FBJsonClassFactory(FBVideo::FromJson); + FBSingleValue^ request = ref new FBSingleValue(path, parameters, factory); + return create_task(request->PostAsync()); + } +} diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoUploader.h b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoUploader.h new file mode 100644 index 0000000..a827e25 --- /dev/null +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/FBVideoUploader.h @@ -0,0 +1,122 @@ +//****************************************************************************** +// +// Copyright (c) 2016 Microsoft Corporation. All rights reserved. +// +// This code is licensed under the MIT License (MIT). +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +//****************************************************************************** + +#pragma once + +#include "FacebookResult.h" +#include "FBVideoContentStream.h" +#include "FBVideo.h" + +namespace winsdkfb +{ + //! Specifies which graph api method to use to upload the video. + public enum class VideoUploadBehavior + { + SingleRequest, // one http request is made + MultiRequest // video is uploaded in chunks + }; + + /** + * @brief Provides video uploading from a local file to Facebook. + */ + public ref class FBVideoUploader sealed + { + public: + /** + * Uploads a video from a local file to Facebook. + * @param videoFile The video file to upload + * @return FBResult indicating the result of the operation. On success, + * it will contain an FBVideo object and on failure it will contain an + * FBError object. + */ + static Windows::Foundation::IAsyncOperation^ UploadVideoAsync( + Windows::Storage::StorageFile^ videoFile + ); + + /** + * Uploads a video from a local file to Facebook. + * @param videoFile The video file to upload + * @param uploadBehavior The way the video should be uploaded + * @return FBResult indicating the result of the operation. On success, + * it will contain an FBVideo object and on failure it will contain an + * FBError object. + */ + static Windows::Foundation::IAsyncOperation^ UploadVideoAsync( + Windows::Storage::StorageFile^ videoFile, + VideoUploadBehavior uploadBehavior + ); + + private: + /** + * Uploads a video in one HTTP request. For videos that are small enough + * to meet Facebook's guidelines on small video uploading. + * @param videoFile The video file to upload + * @return FBResult indicating the result of the operation, will contain + * an FBVideo object on success and and FBError object on error. + */ + static Concurrency::task UploadSmallVideo( + Windows::Storage::StorageFile^ videoFile + ); + + /** + * Uploads a video with multiple HTTP requests. For videos that are too + * big to be uploading with UploadSmallVideo(). + * @param videoFile The video file to upload + * @param fileSize The size of the video file, in bytes + * @return FBResult indicating the result of the operation, will contain + * an FBVideo object on success and and FBError object on error. + */ + static Concurrency::task UploadLargeVideo( + Windows::Storage::StorageFile^ videoFile, + unsigned long long fileSize + ); + + /** + * Starts the large video uploading process. + * @param fileSize The size of the video file to upload, in bytes + * @return FBResult indicating the result of the operation, will contain + * an FBVideo object on success and and FBError object on error. + */ + static Concurrency::task StartRequest( + unsigned long long fileSize + ); + + /** + * Portions the video file into chunks and uploads them to sequentially. + * @param stream The stream of the video file data + * @param video The FBVideo object containing the upload process data + * from previous operations + * @param title The name of the video file to upload + * @return FBResult indicating the result of the operation, will contain + * an FBVideo object on success and and FBError object on error. + */ + static Concurrency::task TransferRequest( + FBVideoContentStream^ stream, + Graph::FBVideo^ video, + Platform::String^ title + ); + + /** + * Signals to Facebook the end of the large video upload process + * @param uploadSessionId The session ID of the video upload + * @return FBResult indicating the result of the operation, will contain + * an FBVideo object on success and and FBError object on error. + */ + static Concurrency::task FinishRequest( + Platform::String^ uploadSessionId + ); + }; +} \ No newline at end of file diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/codegen.cmd b/winsdkfb/winsdkfb/winsdkfb.Shared/codegen.cmd index 9f16981..0b8d429 100644 --- a/winsdkfb/winsdkfb/winsdkfb.Shared/codegen.cmd +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/codegen.cmd @@ -31,6 +31,7 @@ call :GenFile FBProfilePicture.cpp FBProfilePicture.cpp.tt call :GenFile FBProfilePictureData.cpp FBProfilePictureData.cpp.tt call :GenFile FBAppRequest.cpp FBAppRequest.cpp.tt call :GenFile FBObject.cpp FBObject.cpp.tt +call :GenFile FBVideo.cpp FBVideo.cpp.tt REM header files call :GenFile FBCursors.h FBCursors.h.tt call :GenFile FBGroup.h FBGroup.h.tt @@ -43,6 +44,7 @@ call :GenFile FBProfilePicture.h FBProfilePicture.h.tt call :GenFile FBProfilePictureData.h FBProfilePictureData.h.tt call :GenFile FBAppRequest.h FBAppRequest.h.tt call :GenFile FBObject.h FBObject.h.tt +call :GenFile FBVideo.h FBVideo.h.tt goto End diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/winsdkfb.Shared.vcxitems b/winsdkfb/winsdkfb/winsdkfb.Shared/winsdkfb.Shared.vcxitems index 161dbfc..f8ef0a6 100644 --- a/winsdkfb/winsdkfb/winsdkfb.Shared/winsdkfb.Shared.vcxitems +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/winsdkfb.Shared.vcxitems @@ -38,6 +38,7 @@ + @@ -48,11 +49,13 @@ + + @@ -76,6 +79,7 @@ + @@ -91,8 +95,10 @@ + + Create @@ -131,6 +137,8 @@ + + @@ -150,4 +158,7 @@ false + + + \ No newline at end of file diff --git a/winsdkfb/winsdkfb/winsdkfb.Shared/winsdkfb.Shared.vcxitems.filters b/winsdkfb/winsdkfb/winsdkfb.Shared/winsdkfb.Shared.vcxitems.filters index 6d5a251..4088e26 100644 --- a/winsdkfb/winsdkfb/winsdkfb.Shared/winsdkfb.Shared.vcxitems.filters +++ b/winsdkfb/winsdkfb/winsdkfb.Shared/winsdkfb.Shared.vcxitems.filters @@ -1,4 +1,4 @@ - + @@ -81,10 +81,21 @@ Source Files - Source Files + + Source Files + + + Source Files + + + Generated code + + + Source Files + Source Files @@ -182,7 +193,6 @@ Header Files - Header Files @@ -192,6 +202,18 @@ Header Files + + Header Files + + + Header Files + + + Generated code + + + Header Files + Header Files @@ -296,6 +318,12 @@ Object Templates + + Object Templates + + + Object Templates + @@ -318,4 +346,9 @@ {aa3295ea-aec9-432a-92b9-a5cfba6e5fc7} + + + Object Templates + + \ No newline at end of file