text: /***
  * ==++==
  *
  * Copyright (c) Microsoft Corporation. All rights reserved. 
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  * http://www.apache.org/licenses/LICENSE-2.0
  * 
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
  * ==--==
  * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
  *
  * live_connect.h
  *
  * Simple API for connecting to Windows Live services, such as OneDrive and Hotmail.
  * Only supported for App Store apps.
  *
  * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
  ****/
  
  #pragma once
  
  #include <collection.h>
  #include "cpprest/basic_types.h"
  #include "cpprest/http_client.h"
  #include "cpprest/streams.h"
  #include "cpprest/filestream.h"
  
  namespace web { namespace live {
  
  #if defined(__cplusplus_winrt)
  
      /// <summary>
      /// This namespace contains symbols for the standard Windows Live permission scopes, which
      /// determine what privileges the application has to access data. What operations are allowed
      /// and what details are returned will depend on privileges requested and granted.
      ///
      /// See MSDN documentation for Live Connect at http://msdn.microsoft.com/en-us/live/ for details
      /// on the precise meaning of these scopes.
      /// </summary>
      namespace scopes 
      {
          static const utility::string_t wl_birthday           = U("wl.birthday");
          static const utility::string_t wl_basic              = U("wl.basic");
          static const utility::string_t wl_calendars          = U("wl.calendars");
          static const utility::string_t wl_calendars_update   = U("wl.calendars_update");
          static const utility::string_t wl_contacts_birthday  = U("wl.contacts_birthday");
          static const utility::string_t wl_contacts_create    = U("wl.contacts_create");
          static const utility::string_t wl_contacts_calendars = U("wl.contacts_calendars");
          static const utility::string_t wl_contacts_photos    = U("wl.contacts_photos");
          static const utility::string_t wl_contacts_skydrive  = U("wl.contacts_skydrive");
          static const utility::string_t wl_emails             = U("wl.emails");
          static const utility::string_t wl_events_create      = U("wl.events_create");
          static const utility::string_t wl_messenger          = U("wl.messenger");
          static const utility::string_t wl_offline_access     = U("wl.offline_access");
          static const utility::string_t wl_phone_numbers      = U("wl.phone_numbers");
          static const utility::string_t wl_photos             = U("wl.photos");
          static const utility::string_t wl_postal_addresses   = U("wl.postal_addresses");
          static const utility::string_t wl_share              = U("wl.share");
          static const utility::string_t wl_signin             = U("wl.signin");
          static const utility::string_t wl_skydrive           = U("wl.skydrive");
          static const utility::string_t wl_skydrive_update    = U("wl.skydrive_update");
          static const utility::string_t wl_work_profile       = U("wl.work_profile");
      }
  
      /// <summary>
      /// Represents a session connected to Windows Live services like Calendar, Contacts, OneDrive, and the
      /// user profile, by using the Live Connect REST API. It is a thin layer on top of the Casablanca HTTP
      /// library, tailored to the usage scenarios for Windows Live clients.
      /// </summary>
      /// <remarks>
      ///   See http://msdn.microsoft.com/onedrive/ for details on using the OneDrive REST API.
      ///
      ///   Note: when passing resource paths into the functions in this class, it is not necessary to add the
      ///   leading '/' path character. It will be added automatically when constructing the HTTP request.
      ///
      ///   Most Windows Live requests will return data in the form of plain text or JSON. The APIs in this
      ///   class will typically return data as JSON. The most significant exception to this is download(),
      ///   which will not return any data to be extracted, it will place the result in the stream or file
      ///   passed into it. Also, remove() will return data only to provide error information: for non-error
      ///   cases, the JSON value will be <c>null</c>.
      ///
      ///   This class relies on PPL tasks to represent asynchrony: the examples contained within the comments
      ///   show synchronous forms of invoking these APIs. In real use, applications should be relying on 
      ///   .then() instead of .get() or .wait().
      /// </remarks>
      class live_client
      {
      public:
          /// <summary>
          /// Constructs a new Windows Live client
          /// </summary>
          live_client() :
              m_client(L"https://apis.live.net/v5.0/"),
              m_authenticator(ref new Windows::Security::Authentication::OnlineId::OnlineIdAuthenticator)
          {
          }
  
          /// <summary>
          /// Authenticates the current user and requests permission to access a listed Live services.
          /// </summary>
          /// <param name="services">A string containing a space-separated list of access scopes to request</param>
          /// <returns><c>true</c> if authentication succeeded, <c>false</c> otherwise.</returns>
          pplx::task<bool> login(utility::string_t scopes)
          {
              this->m_token.clear();
  
              auto request = ref new Windows::Security::Authentication::OnlineId::OnlineIdServiceTicketRequest(ref new Platform::String(scopes.c_str()), "DELEGATION");
  
              return pplx::create_task(m_authenticator->AuthenticateUserAsync(request))
              .then([this](Windows::Security::Authentication::OnlineId::UserIdentity^ ident)
              {      
                  if ( ident->Tickets->Size > 0 )
                  {
                      auto ticket = ident->Tickets->GetAt(0);
        
                      m_token = std::wstring(ticket->Value->Data());
                      return true;
                  }
                  return false;
              });
          }
  
          /// <summary>
          /// Authenticates the current user and requests permission to access a listed Live services.
          /// </summary>
          /// <param name="begin">
          ///   The starting point of an iterator over a std::string collection, (for example a vector of
          ///   strings), holding the names of scopes to get permission for.
          /// </param>
          /// <param name="end">The end point for the same collection.</param>
          /// <returns><c>true</c> if authentication succeeded, <c>false</c> otherwise.</returns>
          template<typename Iter>
          pplx::task<bool> login(Iter begin, Iter end)
          {
              utility::string_t services;
              
              for (Iter iter = begin; iter != end; ++iter)
              {
                  services.append(*iter);
                  services.append(utility::string_t(L" "));
              }
  
              return login(services);
          }
  
          /// <summary>
          /// Dismisses any and all permissions that have been granted to the user.
          /// </summary>
          /// <returns><c>true</c> if logout succeeded, <c>false</c> otherwise.</returns>
          /// <remarks>
          ///   Whether the logout attempt was successful or not, the application will
          ///   be required to log in again, since the access token will be cleared.
          /// </remarks>
          pplx::task<bool> logout()
          {
              this->m_token.clear();
  
              if (!m_authenticator->CanSignOut)
                  return pplx::task_from_result(false);
  
              return pplx::create_task(m_authenticator->SignOutUserAsync()).then([this]
              {
                  return true;
              });
          }
  
          /// <summary>
          /// Retrieves the access token in use by this client instance.
          /// </summary>
          /// <returns>The current token: a string. An invalid token is indicated by an empty string.</returns>
          const utility::string_t& access_token() const 
          {
                  return m_token;
          }
  
          /// <summary>
          /// Retrieves data from Windows Live.
          /// </summary>
          /// <param name="resource">The Windows Live resource to retrieve.</param>
          /// <returns>The JSON value resulting from the request.</returns>
          /// <example>To get the current user profile: <code>json::value profile = live_clnt.get(L"me").get().extract_json().get();</code></example>
          pplx::task<web::json::value> get(const utility::string_t& resource)
          {
              return _make_request(web::http::methods::GET, resource).then([](web::http::http_response response) { return _json_extract(response); });
          }
  
          /// <summary>
          /// Deletes data from Windows Live.
          /// </summary>
          /// <param name="resource">The Windows Live resource to delete.</param>
          /// <returns>The JSON value resulting from the request.</returns>
          /// <example>To delete a file: <code>live_clnt.remove(L"file.NNNNNNNNNNNN").wait();</code>, where file.NNNNNNNNNNNN is a file identifier.</example>
          pplx::task<web::json::value> remove(const utility::string_t& resource)
          {
              return _make_request(web::http::methods::DEL, resource).then([](web::http::http_response response) { return _json_extract(response); });
          }
  
          /// <summary>
          /// Modifies data in Windows Live.
          /// </summary>
          /// <param name="resource">The Windows Live resource to modify.</param>
          /// <returns>The JSON value resulting from the request.</returns>
          /// <remarks>In most cases, the response will contain data about the modified resource.</remarks>
          pplx::task<web::json::value> put(const utility::string_t& resource, const web::json::value& data)
          {
              return _make_request(web::http::methods::PUT, resource, data).then([](web::http::http_response response) { return _json_extract(response); });
          }
  
          /// <summary>
          /// Adds data to Windows Live.
          /// </summary>
          /// <param name="resource">The Windows Live resource (such as a folder) where data should be added.</param>
          /// <returns>The JSON value resulting from the request.</returns>
          /// <remarks>In most cases, the response will contain data about the added resource.</remarks>
          /// <example>To add a contact: <code>json::value contact = ...; live_clnt.post(L"me/contacts", contact).wait();</code></example>
          pplx::task<web::json::value> post(const utility::string_t& resource, const web::json::value& data)
          {
              return _make_request(web::http::methods::POST, resource, data).then([](web::http::http_response response) { return _json_extract(response); });
          }
  
          /// <summary>
          /// Copies data in Windows Live.
          /// </summary>
          /// <param name="resource">The Windows Live resource to copy.</param>
          /// <param name="destination">The location where the resource copy should be placed.</param>
          /// <returns>The JSON value resulting from the request.</returns>
          /// <remarks>In most cases, the response will contain data about the added resource.</remarks>
          /// <example>To copy a file: <code>live_clnt.copy(L"file.NNNNNNNNNNNN", L"folder.MMMMMMMMMMMM").wait();</code></example>
          pplx::task<web::json::value> copy(const utility::string_t& resource, const utility::string_t& destination)
          {
              return _make_request(U("COPY"), resource, destination).then([](web::http::http_response response) { return _json_extract(response); });
          }
  
          /// <summary>
          /// Moves data in Windows Live.
          /// </summary>
          /// <param name="resource">The Windows Live resource to move.</param>
          /// <param name="destination">The location where the resource should be placed.</param>
          /// <returns>The JSON value resulting from the request.</returns>
          /// <remarks>In most cases, the response will contain data about the resource in its new location.</remarks>
          /// <example>To copy a file: <code>live_clnt.copy(L"file.NNNNNNNNNNNN", L"folder.MMMMMMMMMMMM").wait();</code></example>
          pplx::task<web::json::value> move(const utility::string_t& resource, const utility::string_t& destination)
          {
              return _make_request(U("MOVE"), resource, destination).then([](web::http::http_response response) { return _json_extract(response); });
          }
  
          /// <summary>
          /// Download a file from OneDrive.
          /// </summary>
          /// <param name="file_id">The OneDrive file id to download.</param>
          /// <param name="stream">A stream into which the contents of the file should be placed.</param>
          /// <returns>The size of the downloaded resource.</returns>
          /// <remarks></remarks>
          /// <example>To download a file: <code>ostream stream = ...; live_clnt.download(L"file.NNNNNNNNNNNN", ostream).get().content_ready().wait();</code></example>
          pplx::task<size_t> download(const utility::string_t& file_id, concurrency::streams::ostream stream)
          {
              web::http::uri_builder bldr;
              bldr.append(file_id);
              bldr.append_path(U("content"));
  
              web::http::http_request req(web::http::methods::GET);
              req.set_request_uri(bldr.to_string());
  
              return _make_request(req)
                  .then([stream](web::http::http_response response) -> pplx::task<size_t>
                  {
                      if ( response.status_code() >= 400 )
                      {
                          return response.extract_string().then(
                              [](utility::string_t message) -> pplx::task<size_t>
                              {
                                  return pplx::task_from_exception<size_t>(std::exception(utility::conversions::to_utf8string(message).c_str()));
                              });
                      }
                      return response.body().read_to_end(stream.streambuf());
                  });
          }
  
          /// <summary>
          /// Download a file from OneDrive.
          /// </summary>
          /// <param name="file_id">The OneDrive file id to download.</param>
          /// <param name="file">A StorageFile reference identifying the target for the downloaded data.</param>
          /// <returns>The size of the downloaded resource.</returns>
          /// <remarks></remarks>
          pplx::task<size_t> download(const utility::string_t& file_id, Windows::Storage::StorageFile^ file)
          {
              if ( file == nullptr )
                  throw std::invalid_argument("file reference cannot be null");
  
              return concurrency::streams::file_stream<uint8_t>::open_ostream(file).then(
                  [file_id,this](concurrency::streams::ostream stream)
                  {
                      web::http::uri_builder bldr;
                      bldr.append(file_id);
                      bldr.append_path(U("content"));
  
                      web::http::http_request req(web::http::methods::GET);
                      req.set_request_uri(bldr.to_string());
  
                      return _make_request(req)
                          .then([stream](web::http::http_response response) -> pplx::task<size_t>
                          {
                              if ( response.status_code() >= 400 )
                              {
                                  return response.extract_string().then(
                                      [](utility::string_t message) -> pplx::task<size_t>
                                      {
                                          return pplx::task_from_exception<size_t>(std::exception(utility::conversions::to_utf8string(message).c_str()));
                                      });
                              }
                              return response.body().read_to_end(stream.streambuf());
                          })
                          .then([stream](pplx::task<size_t> ret_task)
                          {
                              return stream.flush().then([stream, ret_task]()
                              {
                                  return stream.close();
                              }).then([ret_task]()
                              {
                                  return ret_task;    
                              });
                          });
                  });
          }
  
          /// <summary>
          /// Upload a file to OneDrive.
          /// </summary>
          /// <param name="path">The path of the file location in OneDrive. It should be of the form "folder.NNNNNNNNNN/files/file_name.ext"</param>
          /// <param name="stream">A stream from which data for the file will be read.</param>
          /// <param name="content_length">The size of the data to upload.</param>
          /// <returns>The JSON value resulting from the request, containing metadata about the uploaded file.</returns>
          /// <remarks>
          ///   The stream must contain at least as many bytes as indicated by 'content_length', starting from its current read position. 
          /// </remarks>
          pplx::task<web::json::value> upload(const utility::string_t& path, concurrency::streams::istream stream, size_t content_length)
          {
              web::http::uri_builder bldr;
              bldr.append(path);
  
              web::http::http_request req(web::http::methods::PUT);
              req.set_request_uri(bldr.to_string());
              req.set_body(stream, content_length, U(""));
  
              return _make_request(req)
                  .then([](web::http::http_response response)
                  {
                      return _json_extract(response);
                  });
          }
  
          /// <summary>
          /// Upload a file to OneDrive.
          /// </summary>
          /// <param name="path">The path of the file location in OneDrive. It should be of the form "folder.NNNNNNNNNN/files/file_name.ext"</param>
          /// <param name="file">A StorageFile reference identifying the source of the uploaded data.</param>
          /// <param name="content_length">The size of the data to upload.</param>
          /// <returns>The JSON value resulting from the request, containing metadata about the uploaded file.</returns>
          /// <remarks>The entire file will be uploaded.</remarks>
          pplx::task<web::json::value> upload(const utility::string_t& path, Windows::Storage::StorageFile^ file)
          {
              if ( file == nullptr )
                  throw std::invalid_argument("file reference cannot be null");
  
              return pplx::create_task(file->GetBasicPropertiesAsync()).then(
                  [path,this,file](Windows::Storage::FileProperties::BasicProperties^ props)
                  {
                      if (props == nullptr)
                          throw std::exception("failed to retrieve file properties; cannot determine its size");
                      size_t size = (size_t)props->Size;
  
                      return concurrency::streams::file_stream<uint8_t>::open_istream(file).then(
                          [path,this,size](concurrency::streams::istream stream)
                          {
                              web::http::uri_builder bldr;
                              bldr.append(path);
  
                              web::http::http_request req(web::http::methods::PUT);
                              req.set_request_uri(bldr.to_string());
                              req.set_body(stream, size, U(""));
  
                              return _make_request(req)
                                  .then([](web::http::http_response response)
                                  {
                                      return response.content_ready(); 
                                  })
                                  .then([stream](pplx::task<web::http::http_response> response)
                                  {
                                      return stream.close().then([response]()
                                      {
                                          return _json_extract(response.get());
                                      });
                                  });
                          });
                  });
          }
  
      private:
  
          static pplx::task<web::json::value> _json_extract(web::http::http_response response)
          {
              switch(response.status_code())
              {
              case web::http::status_codes::NoContent:
                  return pplx::task_from_result(web::json::value::null());
              default:
                  if ( response.status_code() >= 400 )
                  {
                      return response.extract_string().then(
                          [](utility::string_t message) -> pplx::task<web::json::value>
                          {
                              return pplx::task_from_exception<web::json::value>(std::exception(utility::conversions::to_utf8string(message).c_str()));
                          });
                  }
                  return response.extract_json();
              }
          }
  
          pplx::task<web::http::http_response> _make_request(web::http::method method, const utility::string_t& path)
          {
              web::http::uri_builder bldr;
              bldr.append(path);
  
              web::http::http_request req(method);
              req.set_request_uri(bldr.to_string());
              return _make_request(req);
          }
  
  
          pplx::task<web::http::http_response> _make_request(web::http::method method, const utility::string_t& path, const web::json::value& data)
          {
              web::http::uri_builder bldr;
              bldr.append(path);
  
              web::http::http_request req(method);
              req.set_request_uri(bldr.to_string());
              req.set_body(data);
              return _make_request(req);
          }
  
          pplx::task<web::http::http_response> _make_request(web::http::method method, const utility::string_t& path, const utility::string_t& destination)
          {
              web::http::uri_builder bldr;
              bldr.append(path);
  
              web::json::value data;
              data[U("destination")] = web::json::value::string(destination);
  
              web::http::http_request req(method);
              req.set_request_uri(bldr.to_string());
              req.set_body(data);
              return _make_request(req);
          }
  
          pplx::task<web::http::http_response> _make_request(web::http::http_request req)
          {
              if (!m_token.empty())
                  req.headers().add(U("Authorization"), U("Bearer ")+m_token);
              return m_client.request(req);
          }
  
          Windows::Security::Authentication::OnlineId::OnlineIdAuthenticator^ m_authenticator;
          web::http::client::http_client m_client;
          utility::string_t m_token;
      };
  #endif
  }}