Version 1.2.1 alexa-client-sdk

Changes in this update
* **Enhancements**..
  * Added comments to `AlexaClientSDKConfig.json`. These descriptions provide additional guidance for what is expected for each field...
  * Enabled pause and resume controls for Pandora...

* **Bug Fixes**..
  * Bug fix for [issue #329](https://github.com/alexa/avs-device-sdk/issues/329) - `HTTP2Transport` instances no longer leak when `SERVER_SIDE
  * Bug fix for [issue #189](https://github.com/alexa/avs-device-sdk/issues/189) - Fixed a race condition in the `Timer` class that sometimes
  * Bug fix for a race condition that caused `SpeechSynthesizer` to ignore subsequent `Speak` directives...
  * Bug fix for corrupted mime attachments.
This commit is contained in:
Andrey Mikheev 2017-11-16 18:49:28 -08:00
parent 2cc16d8cc4
commit a2b84e329c
27 changed files with 748 additions and 185 deletions

View File

@ -363,6 +363,9 @@ private:
/// Whether or not the @c networkLoop is stopping. Serialized by @c m_mutex.
bool m_isStopping;
/// Whether or not the onDisconnected() notification has been sent. Serialized by @c m_mutex.
bool m_disconnectedSent;
/// Queue of @c MessageRequest instances to send. Serialized by @c m_mutex.
std::deque<std::shared_ptr<avsCommon::avs::MessageRequest>> m_requestQueue;

View File

@ -74,11 +74,13 @@ public:
void setObserver(std::shared_ptr<MessageRouterObserverInterface> observer) override;
void onConnected() override;
void onConnected(std::shared_ptr<TransportInterface> transport) override;
void onDisconnected(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override;
void onDisconnected(
std::shared_ptr<TransportInterface> transport,
avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override;
void onServerSideDisconnect() override;
void onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) override;
void consumeMessage(const std::string& contextId, const std::string& message) override;

View File

@ -60,9 +60,11 @@ public:
void onContextAvailable(const std::string& jsonContext) override;
void onContextFailure(const avsCommon::sdkInterfaces::ContextRequestError error) override;
void onServerSideDisconnect() override;
void onConnected() override;
void onDisconnected(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override;
void onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) override;
void onConnected(std::shared_ptr<TransportInterface> transport) override;
void onDisconnected(
std::shared_ptr<TransportInterface> transport,
avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) override;
void addObserver(std::shared_ptr<PostConnectObserverInterface> observer) override;
void removeObserver(std::shared_ptr<PostConnectObserverInterface> observer) override;

View File

@ -36,19 +36,27 @@ public:
/**
* Called when a connection to AVS is established.
*
* @param transport The transport that has connected.
*/
virtual void onConnected() = 0;
virtual void onConnected(std::shared_ptr<TransportInterface> transport) = 0;
/**
* Called when we disconnect from AVS.
*
* @param transport The transport that is no longer connected (or attempting to connect).
* @param reason The reason that we disconnected.
*/
virtual void onDisconnected(avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) = 0;
virtual void onDisconnected(
std::shared_ptr<TransportInterface> transport,
avsCommon::sdkInterfaces::ConnectionStatusObserverInterface::ChangedReason reason) = 0;
/**
* Called when the server asks the client to reconnect
*
* @param transport The transport that has received the disconnect request.
*/
virtual void onServerSideDisconnect() = 0;
virtual void onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) = 0;
};
} // namespace acl

View File

@ -169,6 +169,7 @@ HTTP2Transport::HTTP2Transport(
m_isNetworkThreadRunning{false},
m_isConnected{false},
m_isStopping{false},
m_disconnectedSent{false},
m_postConnectObject{postConnectObject} {
m_observers.insert(observer);
@ -700,9 +701,10 @@ void HTTP2Transport::setIsConnectedFalse() {
auto disconnectReason = ConnectionStatusObserverInterface::ChangedReason::INTERNAL_ERROR;
{
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_isConnected) {
if (m_disconnectedSent) {
return;
}
m_disconnectedSent = true;
m_isConnected = false;
disconnectReason = m_disconnectReason;
}
@ -775,7 +777,7 @@ void HTTP2Transport::notifyObserversOnServerSideDisconnect() {
lock.unlock();
for (auto observer : observers) {
observer->onServerSideDisconnect();
observer->onServerSideDisconnect(shared_from_this());
}
}
@ -785,7 +787,7 @@ void HTTP2Transport::notifyObserversOnDisconnect(ConnectionStatusObserverInterfa
lock.unlock();
for (auto observer : observers) {
observer->onDisconnected(reason);
observer->onDisconnected(shared_from_this(), reason);
}
}
@ -795,7 +797,7 @@ void HTTP2Transport::notifyObserversOnConnected() {
lock.unlock();
for (auto observer : observers) {
observer->onConnected();
observer->onConnected(shared_from_this());
}
}

View File

@ -112,7 +112,7 @@ void MessageRouter::setAVSEndpoint(const std::string& avsEndpoint) {
}
}
void MessageRouter::onConnected() {
void MessageRouter::onConnected(std::shared_ptr<TransportInterface> transport) {
std::unique_lock<std::mutex> lock{m_connectionMutex};
if (m_isEnabled) {
setConnectionStatusLocked(
@ -121,45 +121,34 @@ void MessageRouter::onConnected() {
}
}
void MessageRouter::onDisconnected(ConnectionStatusObserverInterface::ChangedReason reason) {
void MessageRouter::onDisconnected(
std::shared_ptr<TransportInterface> transport,
ConnectionStatusObserverInterface::ChangedReason reason) {
std::lock_guard<std::mutex> lock{m_connectionMutex};
if (ConnectionStatusObserverInterface::Status::CONNECTED == m_connectionStatus) {
// Reset m_activeTransport of it is not longer connected.
if (m_activeTransport && !m_activeTransport->isConnected()) {
safelyResetActiveTransportLocked();
}
// Trim m_transports to just those transports still connected. Also build list of disconnected transports.
std::vector<std::shared_ptr<TransportInterface>> connected;
std::vector<std::shared_ptr<TransportInterface>> disconnected;
for (auto transport : m_transports) {
if (transport->isConnected()) {
connected.push_back(transport);
} else {
disconnected.push_back(transport);
}
}
m_transports.clear();
std::swap(m_transports, connected);
// Release all the disconnected transports.
for (auto transport : disconnected) {
for (auto it = m_transports.begin(); it != m_transports.end(); it++) {
if (*it == transport) {
m_transports.erase(it);
safelyReleaseTransport(transport);
break;
}
}
if (transport == m_activeTransport) {
m_activeTransport.reset();
// Update status. If transitioning to PENDING, also initiate the reconnect.
if (m_isEnabled) {
if (!m_activeTransport) {
if (ConnectionStatusObserverInterface::Status::CONNECTED == m_connectionStatus) {
if (m_isEnabled) {
setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::PENDING, reason);
createActiveTransportLocked();
} else if (m_transports.empty()) {
setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::DISCONNECTED, reason);
}
} else if (m_transports.empty()) {
setConnectionStatusLocked(ConnectionStatusObserverInterface::Status::DISCONNECTED, reason);
}
}
}
void MessageRouter::onServerSideDisconnect() {
void MessageRouter::onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) {
std::unique_lock<std::mutex> lock{m_connectionMutex};
if (m_isEnabled) {
setConnectionStatusLocked(

View File

@ -162,6 +162,11 @@ MimeParser::DataParsedStatus MimeParser::writeDataToAttachment(const char* buffe
void MimeParser::partDataCallback(const char* buffer, size_t size, void* userData) {
MimeParser* parser = static_cast<MimeParser*>(userData);
if (MimeParser::DataParsedStatus::INCOMPLETE == parser->m_dataParsedStatus) {
ACSDK_DEBUG9(LX("partDataCallbackIgnored").d("reason", "attachmentWriterFullBuffer"));
return;
}
if (parser->m_dataParsedStatus != MimeParser::DataParsedStatus::OK) {
ACSDK_ERROR(
LX("partDataCallbackFailed").d("reason", "mimeParsingError").d("status", parser->m_dataParsedStatus));
@ -170,7 +175,7 @@ void MimeParser::partDataCallback(const char* buffer, size_t size, void* userDat
// If we've already processed any of this part in a previous incomplete iteration, let's not process it twice.
if (!parser->shouldProcessBytes(size)) {
ACSDK_DEBUG(LX("partDataCallbackSkipped").d("reason", "bytesAlreadyProcessed"));
ACSDK_DEBUG9(LX("partDataCallbackSkipped").d("reason", "bytesAlreadyProcessed"));
parser->updateCurrentByteProgress(size);
parser->m_dataParsedStatus = MimeParser::DataParsedStatus::OK;
return;
@ -304,6 +309,7 @@ MimeParser::DataParsedStatus MimeParser::feed(char* data, size_t length) {
}
// Initialize this before all the feed() callbacks happen (since this persists from previous call).
m_currentByteProgress = 0;
m_dataParsedStatus = DataParsedStatus::OK;
m_multipartReader.feed(data, length);
@ -350,7 +356,7 @@ void MimeParser::setAttachmentWriterBufferFull(bool isFull) {
if (isFull == m_isAttachmentWriterBufferFull) {
return;
}
ACSDK_DEBUG0(LX("setAttachmentWriterBufferFull").d("full", isFull));
ACSDK_DEBUG9(LX("setAttachmentWriterBufferFull").d("full", isFull));
m_isAttachmentWriterBufferFull = isFull;
}

View File

@ -240,16 +240,18 @@ void PostConnectSynchronizer::notifyObservers() {
}
}
void PostConnectSynchronizer::onServerSideDisconnect() {
void PostConnectSynchronizer::onServerSideDisconnect(std::shared_ptr<TransportInterface> transport) {
ACSDK_DEBUG(LX("onServerSideDisconnect()"));
doShutdown();
}
void PostConnectSynchronizer::onConnected() {
void PostConnectSynchronizer::onConnected(std::shared_ptr<TransportInterface> transport) {
ACSDK_DEBUG(LX("onConnected()"));
}
void PostConnectSynchronizer::onDisconnected(ConnectionStatusObserverInterface::ChangedReason reason) {
void PostConnectSynchronizer::onDisconnected(
std::shared_ptr<TransportInterface> transport,
ConnectionStatusObserverInterface::ChangedReason reason) {
ACSDK_DEBUG(LX("onDisconnected()"));
doShutdown();
}

View File

@ -39,7 +39,7 @@ TEST_F(MessageRouterTest, getConnectionStatusReturnsConnectedAfterConnectionEsta
}
TEST_F(MessageRouterTest, getConnectionStatusReturnsConnectedAfterDisconnected) {
m_router->onDisconnected(ConnectionStatusObserverInterface::ChangedReason::ACL_DISABLED);
m_router->onDisconnected(m_mockTransport, ConnectionStatusObserverInterface::ChangedReason::ACL_DISABLED);
ASSERT_EQ(m_router->getConnectionStatus().first, ConnectionStatusObserverInterface::Status::DISCONNECTED);
}
@ -74,7 +74,7 @@ TEST_F(MessageRouterTest, ensureTheMessageRouterObserverIsInformedOfTransportDis
auto reason = ConnectionStatusObserverInterface::ChangedReason::ACL_DISABLED;
disconnectMockTransport(m_mockTransport.get());
m_router->onDisconnected(reason);
m_router->onDisconnected(m_mockTransport, reason);
// wait for the result to propagate by scheduling a task on the client executor
waitOnMessageRouter(SHORT_TIMEOUT_MS);
@ -179,7 +179,7 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) {
m_router->setMockTransport(newTransport);
// Reset the MessageRouterObserver, there should be no interactions with the observer
m_router->onServerSideDisconnect();
m_router->onServerSideDisconnect(oldTransport);
waitOnMessageRouter(SHORT_TIMEOUT_MS);
@ -191,7 +191,7 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) {
// mock the new transports connection
connectMockTransport(newTransport.get());
m_router->onConnected();
m_router->onConnected(newTransport);
waitOnMessageRouter(SHORT_TIMEOUT_MS);
@ -203,7 +203,7 @@ TEST_F(MessageRouterTest, serverSideDisconnectCreatesANewTransport) {
// mock the old transport disconnecting completely
disconnectMockTransport(oldTransport.get());
m_router->onDisconnected(ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST);
m_router->onDisconnected(oldTransport, ConnectionStatusObserverInterface::ChangedReason::ACL_CLIENT_REQUEST);
auto messageRequest = createMessageRequest();

View File

@ -120,7 +120,7 @@ public:
void setupStateToConnected() {
setupStateToPending();
m_router->onConnected();
m_router->onConnected(m_mockTransport);
connectMockTransport(m_mockTransport.get());
}

View File

@ -0,0 +1,269 @@
/*
* MimeParserFuzzTest.cpp
*
* Copyright 2017 Amazon.com, Inc. or its affiliates. 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.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file 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.
*/
/// @file MimeParserFuzzTest.cpp
#include <memory>
#include <random>
#include <thread>
#include <curl/curl.h>
#include <gtest/gtest.h>
#include "ACL/Transport/MessageConsumerInterface.h"
#include "ACL/Transport/HTTP2Stream.h"
#include <AVSCommon/AVS/Attachment/AttachmentReader.h>
#include <AVSCommon/AVS/Attachment/AttachmentWriter.h>
#include "AVSCommon/AVS/Attachment/InProcessAttachment.h"
#include <AVSCommon/SDKInterfaces/MessageObserverInterface.h>
#include <AVSCommon/Utils/Logger/Logger.h>
#include "Common/TestableAttachmentManager.h"
#include "Common/Common.h"
#include "Common/MimeUtils.h"
#include "Common/TestableMessageObserver.h"
#include "MockMessageRequest.h"
#include "TestableConsumer.h"
namespace alexaClientSDK {
namespace acl {
namespace test {
using namespace avsCommon::sdkInterfaces;
using namespace avsCommon::avs::attachment;
/// String to identify log entries originating from this file.
static const std::string TAG("MimeParserFuzzTest");
/**
* Create a LogEntry using this file's TAG and the specified event string.
*
* @param The event string for this @c LogEntry.
*/
#define LX(event) alexaClientSDK::avsCommon::utils::logger::LogEntry(TAG, event)
/// The number of feed() calls to be made in @c MimeParserFuzzTest::feed()
static const int FEED_COUNT = 0x100;
/// Max pseudo-random buffer size to pass to @c feed() (large enough to assure we trigger attachment buffer full)
static const size_t MAX_FEED_SIZE = (InProcessAttachment::SDS_BUFFER_DEFAULT_SIZE_IN_BYTES / FEED_COUNT) * 4;
/// Max buffer size to read from the attachment reader (small enough trigger multiple reads per feed)
static const size_t MAX_READ_SIZE = MAX_FEED_SIZE / 8;
/// Consistent seed (for repeatable tests) with which to generate pseudo-random bytes.
static const unsigned int BYTES_SEED = 1;
/// Consistent seed (for repeatable tests) with which to generate pseudo-random feed sizes.
static const unsigned int FEED_SIZE_SEED = 2;
/// Consistent but different seed (for repeatable tests) with which to generate pseudo-random read sizes.
static const unsigned int READ_SIZE_SEED = FEED_SIZE_SEED + 1;
/// Content ID for mime part.
static const std::string CONTENT_ID = "CONTENT_ID";
/// Context ID for generating attachment ID.
static const std::string CONTEXT_ID = "CONTEXT_ID";
/// CR,LF to separate lines in mime headers and boundaries.
static const std::string CRLF = "\r\n";
/// Dashes used as a prefix or suffix to mime boundaries.
static const std::string DASHES = "--";
/// Mime Boundary string (without CR,LF or dashes).
static const std::string BOUNDARY = "BoundaryBoundaryBoundaryBoundaryBoundaryBoundaryBoundary";
/// Mime prefix to our attachment.
static const std::string ATTACHMENT_PREFIX = CRLF + DASHES + BOUNDARY + CRLF + "Content-ID: " + CONTENT_ID + CRLF +
"Content-Type: application/octet-stream" + CRLF + CRLF;
/// Mime suffix terminating our attachment.
static const std::string ATTACHMENT_SUFFIX = CRLF + DASHES + BOUNDARY + DASHES;
/**
* Class to generate consistent pseudo-random byte stream.
*/
class ByteGenerator {
public:
ByteGenerator();
/**
* Generate the next set of bytes in the stream.
*
* @param[out] buffer The buffer in which to place the generated bytes.
* @param size The number of bytes to generate.
*/
void generateBytes(std::vector<uint8_t>* buffer, size_t size);
private:
/// The underlying random byte generator.
std::independent_bits_engine<std::minstd_rand, CHAR_BIT, uint8_t> m_engine;
};
ByteGenerator::ByteGenerator() {
m_engine.seed(BYTES_SEED);
}
void ByteGenerator::generateBytes(std::vector<uint8_t>* buffer, size_t size) {
for (size_t ix = 0; ix < size; ix++) {
(*buffer)[ix] = m_engine();
}
}
/**
* Our GTest class.
*/
class MimeParserFuzzTest : public ::testing::Test {
public:
/**
* Construct the objects we will use across tests.
*/
void SetUp() override;
/**
* Feed a pseudo-random stream of bytes to the mime parser, making a number of
* calls with pseudo-random sizes.
*/
void feed();
/**
* Read the attachment the mime parser is rendering to, requesting a pseudo-random
* number of bytes with each read.
*/
void read();
/// The AttachmentManager.
std::shared_ptr<AttachmentManager> m_attachmentManager;
/// The MimeParser which we will be primarily testing.
std::shared_ptr<MimeParser> m_parser;
/// Flag to indicate the test has failed and loops should exit.
std::atomic<bool> m_failed;
};
void MimeParserFuzzTest::SetUp() {
m_attachmentManager = std::make_shared<AttachmentManager>(AttachmentManager::AttachmentType::IN_PROCESS);
auto testableConsumer = std::make_shared<TestableConsumer>();
testableConsumer->setMessageObserver(std::make_shared<TestableMessageObserver>());
m_parser = std::make_shared<MimeParser>(testableConsumer, m_attachmentManager);
m_parser->setAttachmentContextId(CONTEXT_ID);
m_parser->setBoundaryString(BOUNDARY);
m_failed = false;
}
void MimeParserFuzzTest::feed() {
m_parser->feed(const_cast<char*>(ATTACHMENT_PREFIX.c_str()), ATTACHMENT_PREFIX.size());
ByteGenerator feedBytesSource;
std::default_random_engine feedSizeGenerator;
feedSizeGenerator.seed(FEED_SIZE_SEED);
std::uniform_int_distribution<int> feedSizeDistribution(1, MAX_FEED_SIZE);
auto feedSizeSource = std::bind(feedSizeDistribution, feedSizeGenerator);
std::vector<uint8_t> feedBuffer(MAX_FEED_SIZE);
size_t totalBytesFed = 0;
int incompleteCount = 0;
for (int ix = 0; !m_failed && ix < FEED_COUNT; ix++) {
auto feedSize = feedSizeSource();
feedBytesSource.generateBytes(&feedBuffer, feedSize);
while (true) {
ACSDK_DEBUG9(LX("callingFeed").d("totalBytesFed", totalBytesFed).d("feedSize", feedSize));
auto status = m_parser->feed((char*)(&feedBuffer[0]), feedSize);
if (MimeParser::DataParsedStatus::OK == status) {
totalBytesFed += feedSize;
break;
} else if (MimeParser::DataParsedStatus::INCOMPLETE == status) {
ACSDK_DEBUG9(LX("feedIncomplete").d("action", "waitAndReDrive"));
++incompleteCount;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
} else {
ASSERT_NE(status, status);
}
}
}
m_parser->feed(const_cast<char*>(ATTACHMENT_SUFFIX.c_str()), ATTACHMENT_SUFFIX.size());
// Make sure there were enough INCOMPLETE return statuses.
ACSDK_DEBUG9(LX("doneFeeding").d("incompleteCount", incompleteCount).d("FEED_COUNT", FEED_COUNT));
ASSERT_GT(incompleteCount, FEED_COUNT);
}
void MimeParserFuzzTest::read() {
ByteGenerator verifyBytesSource;
std::default_random_engine readSizeGenerator;
readSizeGenerator.seed(READ_SIZE_SEED);
std::uniform_int_distribution<int> readSizeDistribution(1, MAX_READ_SIZE);
auto readSizeSource = std::bind(readSizeDistribution, readSizeGenerator);
auto attachmentId = m_attachmentManager->generateAttachmentId(CONTEXT_ID, CONTENT_ID);
auto reader = m_attachmentManager->createReader(attachmentId, AttachmentReader::Policy::BLOCKING);
std::vector<uint8_t> readBuffer(MAX_READ_SIZE);
std::vector<uint8_t> verifyBuffer(MAX_READ_SIZE);
size_t totalBytesRead = 0;
while (true) {
// Delay reads so that AnotherMimeParserTest::feed() blocks on a full attachment buffer
// in a reliable way. This makes the test results consistent run to run.
std::this_thread::sleep_for(std::chrono::milliseconds(5));
auto readSizeIn = readSizeSource();
auto readStatus = AttachmentReader::ReadStatus::OK;
ACSDK_DEBUG9(LX("callingRead").d("totalBytesRead", totalBytesRead).d("readSizeIn", readSizeIn));
auto readSizeOut = reader->read(readBuffer.data(), readSizeIn, &readStatus);
if (readSizeOut != 0) {
ACSDK_DEBUG9(LX("readReturned").d("readSizeOut", readSizeOut));
verifyBytesSource.generateBytes(&verifyBuffer, readSizeOut);
for (size_t ix = 0; ix < readSizeOut; ix++) {
auto good = readBuffer[ix] == verifyBuffer[ix];
if (!good) {
m_failed = true;
ASSERT_EQ(readBuffer[ix], verifyBuffer[ix]) << "UnexpectedByte at: " << totalBytesRead + ix;
}
}
totalBytesRead += readSizeOut;
}
switch (readStatus) {
case AttachmentReader::ReadStatus::OK:
break;
case AttachmentReader::ReadStatus::CLOSED:
ACSDK_DEBUG9(LX("readClosed"));
return;
case AttachmentReader::ReadStatus::OK_WOULDBLOCK:
case AttachmentReader::ReadStatus::OK_TIMEDOUT:
case AttachmentReader::ReadStatus::ERROR_OVERRUN:
case AttachmentReader::ReadStatus::ERROR_BYTES_LESS_THAN_WORD_SIZE:
case AttachmentReader::ReadStatus::ERROR_INTERNAL:
ASSERT_NE(readStatus, readStatus);
break;
}
}
}
/**
* Exercise MimeParser's re-drive, parsing, and attachment buffering by feeding in a large attachment
* consisting of pseudo-random bytes sent in pseudo-random sized chunks. At the same time, read the
* resulting attachment in pseudo-random sized requests. Expect that the data read matches the data
* that was fed.
*/
TEST_F(MimeParserFuzzTest, testRandomFeedAndReadSizesOfRandomData) {
auto readThread = std::thread(&MimeParserFuzzTest::read, this);
feed();
readThread.join();
}
} // namespace test
} // namespace acl
} // namespace alexaClientSDK

View File

@ -367,6 +367,7 @@ void Timer::callTask(
break;
}
}
m_stopping = false;
m_running = false;
}

View File

@ -104,7 +104,7 @@ bool ConfigurationNode::initialize(const std::vector<std::istream*>& jsonStreams
}
IStreamWrapper wrapper(*jsonStream);
Document overlay(&m_document.GetAllocator());
overlay.ParseStream(wrapper);
overlay.ParseStream<kParseCommentsFlag>(wrapper);
if (overlay.HasParseError()) {
ACSDK_ERROR(LX("initializeFailed")
.d("reason", "parseFailure")

View File

@ -439,7 +439,24 @@ TEST_F(TimerTest, deleteDuringTask) {
verifyTimestamps(t0, SHORT_DELAY, SHORT_DELAY, Timer::PeriodType::ABSOLUTE, SHORT_DELAY);
}
/// This test verifies that
/**
* This test verifies that a call to start() succeeds on a timer which was previously stopped while running a task, but
* which is inactive at the time the new start() is called.
*/
TEST_F(TimerTest, startRunningAfterStopDuringTask) {
ASSERT_TRUE(m_timer->start(NO_DELAY, std::bind(&TimerTest::simpleTask, this, MEDIUM_DELAY)).valid());
ASSERT_TRUE(m_timer->isActive());
std::this_thread::sleep_for(SHORT_DELAY);
m_timer->stop();
ASSERT_TRUE(waitForInactive());
m_timestamps.clear();
auto t0 = std::chrono::steady_clock::now();
ASSERT_EQ(
m_timer->start(MEDIUM_DELAY, std::bind(&TimerTest::simpleTask, this, NO_DELAY)).wait_for(TIMEOUT),
std::future_status::ready);
ASSERT_TRUE(waitForInactive());
verifyTimestamps(t0, MEDIUM_DELAY, MEDIUM_DELAY, Timer::PeriodType::ABSOLUTE, NO_DELAY);
}
} // namespace test
} // namespace timing

View File

@ -1,5 +1,17 @@
## ChangeLog
### [1.2.1] - 2017-11-16
* **Enhancements**
* Added comments to `AlexaClientSDKConfig.json`. These descriptions provide additional guidance for what is expected for each field.
* Enabled pause and resume controls for Pandora.
* **Bug Fixes**
* Bug fix for [issue #329](https://github.com/alexa/avs-device-sdk/issues/329) - `HTTP2Transport` instances no longer leak when `SERVER_SIDE_DISCONNECT` is encountered.
* Bug fix for [issue #189](https://github.com/alexa/avs-device-sdk/issues/189) - Fixed a race condition in the `Timer` class that sometimes caused `SpeechSynthesizer` to get stuck in the "Speaking" state.
* Bug fix for a race condition that caused `SpeechSynthesizer` to ignore subsequent `Speak` directives.
* Bug fix for corrupted mime attachments.
### [1.2.0] - 2017-10-27
* **Enhancements**
* Updated MediaPlayer to solve stability issues
@ -14,7 +26,7 @@
* Moved shared libcurl functionality to AVSCommon/Utils
* Added a CMake option to exclude tests from the "make all" build. Use "cmake <absolute-path-to-source>
-DACSDK_EXCLUDE_TEST_FROM_ALL=ON" to enable it. When this option is enabled "make unit" and "make integration" still could be used to build and run the tests
* **Bug fixes**:
* Previously scheduled alerts now play following a restart
* General stability fixes
@ -23,9 +35,9 @@
* Attempting to end a tap-to-talk interaction with the tap-to-talk button wouldn't work
* SharedDataStream could encounter a race condition due to a combination of a blocking Reader and a Writer closing before writing any data
* Bug-fix for the ordering of notifications within alerts scheduling. This fixes the issue where a local-stop on an alert would also stop a subsequent alert if it were to begin without delay
* **Known Issues**
* Capability agent for Notifications is not included in this release
* Inconsistent playback behavior when resuming content ("Alexa, pause." / "Alexa, resume."). Specifically, handling playback offsets, which causes the content to play from the start. This behavior is also occasionally seen with "Next" /
"Previous".
@ -33,7 +45,7 @@
* Music playback failures may result in an error Text to Speech being rendered repeatedly
* Reminders and named timers don't sound when there is no connection
* GUI cards don't show for Kindle
### [1.1.0] - 2017-10-02
* **Enhancements**
* Better GStreamer error reporting. MediaPlayer used to only report `MEDIA_ERROR_UNKNOWN`, now reports more specific errors as defined in `ErrorType.h`.
@ -50,7 +62,7 @@
* Added `AudioPlayer` unit tests.
* Added teardown for all Integration tests except Alerts.
* Implemented PlaylistParser.
* **Bug fixes**:
* AIP getting stuck in `LISTENING` or `THINKING` and refusing user input on network outage.
* SampleApp crashing if running for 5 minutes after network disconnect.
@ -63,9 +75,9 @@
* [`HTTP2Transport` network thread triggering a join on itself.](https://github.com/alexa/avs-device-sdk/issues/127)
* [`HTTP2Stream` request handling truncating exception messages.](https://github.com/alexa/avs-device-sdk/issues/67)
* [`AudioPlayer` was attempting an incorrect state transition from `STOPPED` to `PLAYING` through a `playbackResumed`.](https://github.com/alexa/avs-device-sdk/issues/138)
* **Known Issues**
* Native components for the following capability agents are **not** included in this release: `Speaker`, `TemplateRuntime`, and `Notifications`
* `ACL`'s asynchronous receipt of audio attachments may manage resources poorly in scenarios where attachments are received but not consumed.
* When an `AttachmentReader` does not deliver data for prolonged periods, `MediaPlayer` may not resume playing the delayed audio.
@ -80,12 +92,12 @@
* `Recognize` event after regaining network connection and during an alarm going off can cause client to get stuck in `Recognizing` state.
* Three Alerts integration tests fail: `handleMultipleTimersWithLocalStop`, `AlertsTest.UserLongUnrelatedBargeInOnActiveTimer`, `AlertsTest.handleOneTimerWithVocalStop`
* `MediaPlayerTest.testSetOffsetSeekableSource` unit test fails intermittently on Linux.
### [1.0.3] - 2017-09-19
* **Enhancements**
* Implemented `setOffSet` in `MediaPlayer`.
* [Updated `LoggerUtils.cpp`](https://github.com/alexa/avs-device-sdk/issues/77).
* **Bug Fixes**
* [Bug fix to address incorrect stop behavior caused when Audio Focus is set to `NONE` and released](https://github.com/alexa/avs-device-sdk/issues/129).
* Bug fix for intermittent failure in `handleMultipleConsecutiveSpeaks`.
@ -94,12 +106,12 @@
* Bug fix for `SpeechSynthesizer` showing the wrong UX state when a burst of `Speak` directives are received.
* Bug fix for recursive loop in `AudioPlayer.Stop`.
>>>>>>> 3553854... Updated CHANGELOG.md and README.md for 1.1
### [1.0.2] - 2017-08-23
* Removed code from AIP which propagates ExpectSpeech initiator strings to subsequent Recognize events. This code will be re-introduced when AVS starts sending initiator strings.
### [1.0.1] - 2017-08-17
* Added a fix to the sample app so that the `StateSynchronization` event is the first that gets sent to AVS.
* Added a `POST_CONNECTED` enum to `ConnectionStatusObserver`.
* `StateSychronizer` now automatically sends a `StateSynchronization` event when it receives a notification that `ACL` is `CONNECTED`.
@ -116,9 +128,9 @@
* `AudioPlayer` sending `PlaybackPaused` during flash briefing.
* Long Delay playing live stations on iHeartRadio.
* Teardown warnings at the end of integration tests.
### [1.0.0] - 2017-08-07
* Added `AudioPlayer` capability agent.
* Supports iHeartRadio.
* `StateSynchronizer` has been updated to better enforce that `System.SynchronizeState` is the first Event sent on a connection to AVS.
@ -146,9 +158,9 @@
* `Tests`:
* `SpeechSynthesizer` unit tests hang on some older versions of GCC due to a tear down issue in the test suite
* Intermittent Alerts integration test failures caused by rigidness in expected behavior in the tests
### [0.6.0] - 2017-07-14
* Added a sample app that leverages the SDK.
* Added `Alerts` capability agent.
* Added the `DefaultClient` class.
@ -175,9 +187,9 @@
* `Tests`:
* `SpeechSynthesizer` unit tests hang on some older versions of GCC due to a tear down issue in the test suite
* Intermittent Alerts integration test failures caused by rigidness in expected behavior in the tests
### [0.5.0] - 2017-06-23
* Updated most SDK components to use new logging abstraction.
* Added a `getConfiguration()` method to `DirectiveHandlerInterface` to register capability agents with Directive Sequencer.
* Added `ACL` stream processing with pause and redrive.
@ -188,9 +200,9 @@
* Fixes for the following GitHub issues:
* [MessageRequest callbacks never triggered if disconnected](https://github.com/alexa/alexa-client-sdk/issues/21)
* [AttachmentReader::read() returns ReadStatus::CLOSED if an AttachmentWriter has not been created yet](https://github.com/alexa/alexa-client-sdk/issues/25)
### [0.4.1] - 2017-06-09
* Implemented Sensory wake word detector functionality.
* Removed the need for a `std::recursive_mutex` in `MessageRouter`.
* Added `AIP` unit tests.
@ -214,13 +226,13 @@
* [HTTP2Transport race may lead to deadlock](https://github.com/alexa/alexa-client-sdk/issues/10)
* [Crash in HTTP2Transport::cleanupFinishedStreams()](https://github.com/alexa/alexa-client-sdk/issues/17)
* [The attachment writer interface should take a `const void*` instead of `void*`](https://github.com/alexa/alexa-client-sdk/issues/24)
### [0.4.0] - 2017-05-31 (patch)
* Added `AuthServer`, an authorization server implementation used to retrieve refresh tokens from LWA.
### [0.4.0] - 2017-05-24
* Added `SpeechSynthesizer`, an implementation of the `SpeechRecognizer` capability agent.
* Implemented a reference `MediaPlayer` based on [GStreamer](https://gstreamer.freedesktop.org/) for audio playback.
* Added `MediaPlayerInterface` that allows you to implement your own media player.
@ -230,9 +242,9 @@
* Known Issues:
* `ACL`'s asynchronous receipt of audio attachments may manage resources poorly in scenarios where attachments are received but not consumed.
* When an `AttachmentReader` does not deliver data for prolonged periods `MediaPlayer` may not resume playing the delayed audio.
### [0.3.0] - 2017-05-17
* Added the `CapabilityAgent` base class that is used to build capability agent implementations.
* Added `ContextManager`, a component that allows multiple capability agents to store and access state. These Events include `Context`, which is used to communicate the state of each capability agent to AVS in the following Events:
* [`Recognize`](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speechrecognizer#recognize)
@ -247,9 +259,9 @@
* Added WakeWord Detector (WWD), which recognizes keywords in audio streams. [0.3.0] implements a wrapper for KITT.ai.
* Added a new implementation of `AttachmentManager` and associated classes for use with SDS.
* Updated `ACL` to support asynchronously sending audio to AVS.
### [0.2.1] - 2017-05-03
* Replaced the configuration file `AuthDelegate.config` with `AlexaClientSDKConfig.json`.
* Added the ability to specify a `CURLOPT_CAPATH` value to be used when libcurl is used by ACL and AuthDelegate. See See Appendix C in the README for details.
* Changes to ADSL interfaces:
@ -262,18 +274,18 @@
* ACL and AuthDelegate now require TLSv1.2.
* `onDirective()` now sends `ExceptionEncountered` for unhandled directives.
* `DirectiveSequencer::shutdown()` no longer sends `ExceptionEncountered()` for queued directives.
### [0.2.0] - 2017-03-27 (patch)
* Added memory profiling for ACL and ADSL. See Appendix A in the README.
* Added a command to build the API documentation.
### [0.2.0] - 2017-03-09
* Added `Alexa Directive Sequencer Library` (ADSL) and `Alexa Focus Manager Library` (AMFL).
* CMake build types and options have been updated.
* Documentation for libcurl optimization included.
### [0.1.0] - 2017-02-10
* Initial release of the `Alexa Communications Library` (ACL), a component which manages network connectivity with AVS, and `AuthDelegate`, a component which handles user authorization with AVS.

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.1 FATAL_ERROR)
# Set project information
project(AlexaClientSDK VERSION 1.2.0 LANGUAGES CXX)
project(AlexaClientSDK VERSION 1.2.1 LANGUAGES CXX)
set(PROJECT_BRIEF "A cross-platform, modular SDK for interacting with the Alexa Voice Service")
include(build/BuildDefaults.cmake)

View File

@ -598,8 +598,13 @@ void AudioPlayer::executeOnFocusChanged(FocusState newFocus) {
case PlayerActivity::STOPPED:
case PlayerActivity::FINISHED:
// We see a focus change to foreground in these states if we are starting to play a new song.
ACSDK_DEBUG1(LX("executeOnFocusChanged").d("action", "playNextItem"));
playNextItem();
if (!m_audioItems.empty()) {
ACSDK_DEBUG1(LX("executeOnFocusChanged").d("action", "playNextItem"));
playNextItem();
} else {
ACSDK_DEBUG1(LX("executeOnFocusChanged").d("action", "releaseChannel"));
m_focusManager->releaseChannel(CHANNEL_NAME, shared_from_this());
}
return;
case PlayerActivity::PAUSED: {
// A focus change to foreground when paused means we should resume the current song.
@ -784,7 +789,12 @@ void AudioPlayer::executeOnPlaybackError(SourceId id, const ErrorType& type, std
}
sendPlaybackFailedEvent(m_token, type, error);
executeStop();
/*
* There's no need to call stop() here as the MediaPlayer has already stopped due to the playback error. Instead,
* call executeOnPlaybackStopped() so that the states in AudioPlayer are reset properly.
*/
executeOnPlaybackStopped(m_sourceId);
}
void AudioPlayer::executeOnPlaybackPaused(SourceId id) {

View File

@ -1147,11 +1147,14 @@ TEST_F(AudioPlayerTest, testFocusChangeToNoneInIdleState) {
}
/**
* DISABLE this test because the behavior has been changed such that we no longer expect
* a PlaybackFailedEvent
*
* Test focus change to Foreground in IDLE state
* Expect a PlaybackFailedEvent
*/
TEST_F(AudioPlayerTest, testFocusChangeToForegroundInIdleState) {
TEST_F(AudioPlayerTest, DISABLED_testFocusChangeToForegroundInIdleState) {
bool messageSentResult = false;
// expect PlaybackFailed since there are no queued AudioItems
@ -1215,11 +1218,14 @@ TEST_F(AudioPlayerTest, testFocusChangesInPlayingState) {
}
/**
* * DISABLE this test because the behavior has been changed such that we no longer expect
* a PlaybackFailedEvent
*
* Test focus changes in STOPPED state
* Expect a PlaybackFailedEvent when attempting to switch to FOREGROUND
*/
TEST_F(AudioPlayerTest, testFocusChangesInStoppedState) {
TEST_F(AudioPlayerTest, DISABLED_testFocusChangesInStoppedState) {
sendPlayDirective();
// push AudioPlayer into stopped state

View File

@ -620,9 +620,19 @@ void SpeechSynthesizer::executePlaybackError(const avsCommon::utils::mediaPlayer
}
m_waitOnStateChange.notify_one();
releaseForegroundFocus();
sendExceptionEncounteredAndReportFailed(m_currentInfo, avsCommon::avs::ExceptionErrorType::INTERNAL_ERROR, error);
resetCurrentInfo();
resetMediaSourceId();
{
std::unique_lock<std::mutex> lock(m_mutex);
while (!m_speakInfoQueue.empty()) {
auto speakInfo = m_speakInfoQueue.front();
m_speakInfoQueue.pop_front();
lock.unlock();
sendExceptionEncounteredAndReportFailed(
speakInfo, avsCommon::avs::ExceptionErrorType::INTERNAL_ERROR, error);
lock.lock();
}
}
}
std::string SpeechSynthesizer::buildState(std::string& token, int64_t offsetInMilliseconds) const {

View File

@ -259,6 +259,17 @@ public:
/// Future to notify when @c sendMessage is called.
std::future<void> m_wakeSendMessageFuture;
/**
* Fulfills the @c m_wakeStoppedPromise. This is invoked in response to a @c stop call.
*/
void wakeOnStopped();
/// Promise to be fulfilled when @c stop is called.
std::promise<void> m_wakeStoppedPromise;
/// Future to notify when @c stop is called.
std::future<void> m_wakeStoppedFuture;
/// An exception sender used to send exception encountered events to AVS.
std::shared_ptr<MockExceptionEncounteredSender> m_mockExceptionSender;
@ -278,7 +289,9 @@ SpeechSynthesizerTest::SpeechSynthesizerTest() :
m_wakeSetFailedPromise{},
m_wakeSetFailedFuture{m_wakeSetFailedPromise.get_future()},
m_wakeSendMessagePromise{},
m_wakeSendMessageFuture{m_wakeSendMessagePromise.get_future()} {
m_wakeSendMessageFuture{m_wakeSendMessagePromise.get_future()},
m_wakeStoppedPromise{},
m_wakeStoppedFuture{m_wakeStoppedPromise.get_future()} {
}
void SpeechSynthesizerTest::SetUp() {
@ -334,6 +347,10 @@ void SpeechSynthesizerTest::wakeOnSendMessage() {
m_wakeSendMessagePromise.set_value();
}
void SpeechSynthesizerTest::wakeOnStopped() {
m_wakeStoppedPromise.set_value();
}
/**
* Test call to handleDirectiveImmediately.
* Expected result is that @c acquireChannel is called with the correct channel. On focus changed @c FOREGROUND, audio
@ -632,6 +649,113 @@ TEST_F(SpeechSynthesizerTest, testBargeInWhilePlaying) {
ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT));
}
/**
* Testing calling stop() in @c MediaPlayer twice won't get the @c SpeechSynthesizer into a bad state.
* Call preHandle with a valid SPEAK directive. Then call handleDirective. Expected result is that @c acquireChannel
* is called once. On Focus Changed to foreground, audio should play.
* Call cancel directive. Expect the stop() to be called once.
* Call onFocusChanged, expect the stop() to be called again, and this time the @c MediaPlayer will return false to
* report an error. Expect when handleDirectiveImmediately with a valid SPEAK directive is called, @c SpeechSynthesizer
* will react correctly.
*/
TEST_F(SpeechSynthesizerTest, testCallingStopTwice) {
auto avsMessageHeader = std::make_shared<AVSMessageHeader>(
NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_TEST, DIALOG_REQUEST_ID_TEST);
std::shared_ptr<AVSDirective> directive =
AVSDirective::create("", avsMessageHeader, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST);
auto avsMessageHeader2 =
std::make_shared<AVSMessageHeader>(NAMESPACE_SPEECH_SYNTHESIZER, NAME_SPEAK, MESSAGE_ID_TEST_2);
std::shared_ptr<AVSDirective> directive2 =
AVSDirective::create("", avsMessageHeader2, PAYLOAD_TEST, m_attachmentManager, CONTEXT_ID_TEST_2);
EXPECT_CALL(*(m_mockFocusManager.get()), acquireChannel(CHANNEL_NAME, _, FOCUS_MANAGER_ACTIVITY_ID))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnAcquireChannel));
EXPECT_CALL(
*(m_mockSpeechPlayer.get()),
attachmentSetSource(A<std::shared_ptr<avsCommon::avs::attachment::AttachmentReader>>()))
.Times(AtLeast(1));
EXPECT_CALL(*(m_mockSpeechPlayer.get()), play(_)).Times(AtLeast(1));
EXPECT_CALL(*(m_mockSpeechPlayer.get()), getOffset(_))
.Times(AtLeast(1))
.WillRepeatedly(Return(OFFSET_IN_CHRONO_MILLISECONDS_TEST));
EXPECT_CALL(
*(m_mockContextManager.get()),
setState(NAMESPACE_AND_NAME_SPEECH_STATE, PLAYING_STATE_TEST, StateRefreshPolicy::ALWAYS, 0))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState));
EXPECT_CALL(
*(m_mockContextManager.get()),
setState(NAMESPACE_AND_NAME_SPEECH_STATE, FINISHED_STATE_TEST, StateRefreshPolicy::NEVER, 0))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSetState));
EXPECT_CALL(*(m_mockMessageSender.get()), sendMessage(_))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnSendMessage));
EXPECT_CALL(*(m_mockFocusManager.get()), releaseChannel(CHANNEL_NAME, _))
.Times(AtLeast(1))
.WillRepeatedly(InvokeWithoutArgs(this, &SpeechSynthesizerTest::wakeOnReleaseChannel));
EXPECT_CALL(*(m_mockSpeechPlayer.get()), stop(_))
.Times(AtLeast(1))
.WillOnce(Invoke([this](avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) {
wakeOnStopped();
return true;
}))
.WillOnce(Invoke([this](avsCommon::utils::mediaPlayer::MediaPlayerInterface::SourceId id) {
wakeOnStopped();
return false;
}))
.WillRepeatedly(Return(true));
EXPECT_CALL(*m_mockDirHandlerResult, setFailed(_)).Times(AtLeast(1));
// send Speak directive and getting focus and wait until playback started
m_speechSynthesizer->CapabilityAgent::preHandleDirective(directive, std::move(m_mockDirHandlerResult));
m_speechSynthesizer->CapabilityAgent::handleDirective(MESSAGE_ID_TEST);
ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT));
m_wakeAcquireChannelPromise = std::promise<void>();
m_wakeAcquireChannelFuture = m_wakeAcquireChannelPromise.get_future();
m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND);
ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted());
ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT));
m_wakeSetStatePromise = std::promise<void>();
m_wakeSetStateFuture = m_wakeSetStatePromise.get_future();
ASSERT_TRUE(std::future_status::ready == m_wakeSendMessageFuture.wait_for(WAIT_TIMEOUT));
m_wakeSendMessagePromise = std::promise<void>();
m_wakeSendMessageFuture = m_wakeSendMessagePromise.get_future();
// cancel directive, this should result in calling stop()
m_speechSynthesizer->CapabilityAgent::cancelDirective(MESSAGE_ID_TEST);
ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(WAIT_TIMEOUT));
m_wakeStoppedPromise = std::promise<void>();
m_wakeStoppedFuture = m_wakeStoppedPromise.get_future();
// goes to background, this should result in calling the 2nd stop() and MediaPlayer returning an error
m_speechSynthesizer->onFocusChanged(FocusState::BACKGROUND);
ASSERT_TRUE(std::future_status::ready == m_wakeStoppedFuture.wait_for(WAIT_TIMEOUT));
ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT));
m_wakeSetStatePromise = std::promise<void>();
m_wakeSetStateFuture = m_wakeSetStatePromise.get_future();
/*
* onPlaybackStopped, this will result in an error with reason=nullptrDirectiveInfo. But this shouldn't break the
* SpeechSynthesizer
*/
m_speechSynthesizer->onPlaybackStopped(m_mockSpeechPlayer->m_sourceId);
ASSERT_TRUE(std::future_status::ready == m_wakeReleaseChannelFuture.wait_for(WAIT_TIMEOUT));
m_wakeReleaseChannelPromise = std::promise<void>();
m_wakeReleaseChannelFuture = m_wakeReleaseChannelPromise.get_future();
// send second speak directive and make sure it working
m_speechSynthesizer->handleDirectiveImmediately(directive2);
ASSERT_TRUE(std::future_status::ready == m_wakeAcquireChannelFuture.wait_for(WAIT_TIMEOUT));
m_speechSynthesizer->onFocusChanged(FocusState::FOREGROUND);
ASSERT_TRUE(std::future_status::ready == m_wakeSetStateFuture.wait_for(WAIT_TIMEOUT));
m_wakeSetStatePromise = std::promise<void>();
m_wakeSetStateFuture = m_wakeSetStatePromise.get_future();
ASSERT_TRUE(m_mockSpeechPlayer->waitUntilPlaybackStarted());
}
} // namespace test
} // namespace speechSynthesizer
} // namespace capabilityAgents

View File

@ -1,28 +1,78 @@
{
"authDelegate":{
// The Client Secret of the Product from developer.amazon.com
"clientSecret":"${SDK_CONFIG_CLIENT_SECRET}",
// Unique device serial number. e.g. 123456
"deviceSerialNumber":"${SDK_CONFIG_DEVICE_SERIAL_NUMBER}",
"refreshToken":"${SDK_CONFIG_REFRESH_TOKEN}",
// Refresh Token populated by running AuthServer.py
"refreshToken":"{SDK_CONFIG_REFRESH_TOKEN}",
// The Client ID of the Product from developer.amazon.com
"clientId":"${SDK_CONFIG_CLIENT_ID}",
// Product ID from developer.amazon.com
"productId":"${SDK_CONFIG_PRODUCT_ID}"
},
"alertsCapabilityAgent":{
// Path to Alerts database file. e.g. /home/ubuntu/Build/alerts.db
// Note: The directory specified must be valid.
// The database file (alerts.db) will be created by SampleApp, do not create it yourself.
// The database file should only be used for alerts (don't use it for other components of SDK)
"databaseFilePath":"${SDK_SQLITE_DATABASE_FILE_PATH}",
// Path to default Alarm sound file. e.g. /home/ubuntu/alert_sounds/alarm_normal.mp3
// Note: The audio file must exist and be a valid file.
"alarmSoundFilePath":"${SDK_ALARM_DEFAULT_SOUND_FILE_PATH}",
// Path to short Alarm sound file. e.g. /home/ubuntu/alert_sounds/alarm_short.wav
// Note: The audio file must exist and be a valid file.
"alarmShortSoundFilePath":"${SDK_ALARM_SHORT_SOUND_FILE_PATH}",
// Path to default timer sound file. e.g. /home/ubuntu/alert_sounds/timer_normal.mp3
// Note: The audio file must exist and be a valid file.
"timerSoundFilePath":"${SDK_TIMER_DEFAULT_SOUND_FILE_PATH}",
// Path to short timer sound file. e.g. /home/ubuntu/alert_sounds/timer_short.wav
// Note: The audio file must exist and be a valid file.
"timerShortSoundFilePath":"${SDK_TIMER_SHORT_SOUND_FILE_PATH}"
},
"settings":{
// Path to Settings database file. e.g. /home/ubuntu/Build/settings.db
// Note: The directory specified must be valid.
// The database file (settings.db) will be created by SampleApp, do not create it yourself.
// The database file should only be used for settings (don't use it for other components of SDK)
"databaseFilePath":"${SDK_SQLITE_SETTINGS_DATABASE_FILE_PATH}",
"defaultAVSClientSettings":{
// Default language for Alexa.
// See https://developer.amazon.com/docs/alexa-voice-service/settings.html#settingsupdated for valid values.
"locale":"${SETTING_LOCALE_VALUE}"
}
},
"certifiedSender":{
"databaseFilePath":"{SDK_CERTIFIED_SENDER_DATABASE_FILE_PATH}"
// Path to Certified Sender database file. e.g. /home/ubuntu/Build/certifiedsender.db
// Note: The directory specified must be valid.
// The database file (certifiedsender.db) will be created by SampleApp, do not create it yourself.
// The database file should only be used for certifiedSender (don't use it for other components of SDK)
"databaseFilePath":"${SDK_CERTIFIED_SENDER_DATABASE_FILE_PATH}"
}
}
// Notes for logging
// The log levels are supported to debug when SampleApp is not working as expected.
// There are 14 levels of logging with DEBUG9 providing the highest level of logging and CRITICAL providing
// the lowest level of logging i.e. if DEBUG9 is specified while running the SampleApp, all the logs at DEBUG9 and
// below are displayed, whereas if CRITICAL is specified, only logs of CRITICAL are displayed.
// The 14 levels are:
// DEBUG9, DEBUG8, DEBUG7, DEBUG6, DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, DEBUG0, INFO, WARN, ERROR, CRTITICAL.
// To selectively see the logging for a particular module, you can specify logging level in this json file.
// Some examples are:
// To only see logs of level INFO and below for ACL and MediaPlayer modules,
// - grep for ACSDK_LOG_MODULE in source folder. Find the log module for ACL and MediaPlayer.
// - Put the following in json:
// "acl":{
// "logLevel":"INFO"
// },
// "mediaPlayer":{
// "logLevel":"INFO"
// }
// To enable DEBUG, build with cmake option -DCMAKE_BUILD_TYPE=DEBUG. By default it is built with RELEASE build.
// And run the SampleApp similar to the following command.
// e.g. TZ=UTC ./SampleApp /home/ubuntu/.../AlexaClientSDKConfig.json /home/ubuntu/KittAiModels/ DEBUG9"

View File

@ -954,11 +954,14 @@ TEST_F(AlertsTest, cancelAlertBeforeItIsActive) {
}
/**
* Disabled until consistent failures can be investigated. Failures are due to the test's expectation of event
* ordering.
*
* Test when the storage is removed before an alert is set
*
* Close the storage before asking for a 5 second timer. SetAlertFailed and DeleteAlertFailed events are then sent.
*/
TEST_F(AlertsTest, RemoveStorageBeforeAlarmIsSet) {
TEST_F(AlertsTest, DISABLED_RemoveStorageBeforeAlarmIsSet) {
m_alertStorage->close();
// Write audio to SDS saying "Set a timer for 5 seconds"

View File

@ -114,13 +114,16 @@ private:
* The @c AudioPipeline consists of the following elements:
* @li @c appsrc The appsrc element is used as the source to which audio data is provided.
* @li @c decoder Decodebin is used as the decoder element to decode audio.
* @li @c decodedQueue A queue is used to store the decoded data.
* @li @c converter An audio-converter is used to convert between audio formats.
* @li @c volume The volume element is used as a volume control.
* @li @c audioSink Sink for the audio.
* @li @c pipeline The pipeline is a bin consisting of the @c appsrc, the @c decoder, the @c converter, and the
* @c audioSink.
*
* The data flow through the elements is appsrc -> decoder -> converter -> volume -> audioSink.
* The data flow through the elements is appsrc -> decoder -> decodedQueue -> converter -> volume -> audioSink.
* Ideally we would want to use playsink or playbin directly to automate as much as possible. However, this
* causes problems with multiple pipelines and volume settings in pulse audio. Pending further investigation.
*/
struct AudioPipeline {
/// The source element.
@ -129,6 +132,9 @@ private:
/// The decoder element.
GstElement* decoder;
/// A queue for decoded elements.
GstElement* decodedQueue;
/// The converter element.
GstElement* converter;
@ -145,6 +151,7 @@ private:
AudioPipeline() :
appsrc{nullptr},
decoder{nullptr},
decodedQueue{nullptr},
converter{nullptr},
volume{nullptr},
audioSink{nullptr},

View File

@ -533,6 +533,14 @@ bool MediaPlayer::init() {
}
bool MediaPlayer::setupPipeline() {
m_pipeline.decodedQueue = gst_element_factory_make("queue", "decodedQueue");
// Do not send signals or messages. Let the decoder buffer messages dictate application logic.
g_object_set(m_pipeline.decodedQueue, "silent", TRUE, NULL);
if (!m_pipeline.decodedQueue) {
ACSDK_ERROR(LX("setupPipelineFailed").d("reason", "createQueueElementFailed"));
return false;
}
m_pipeline.converter = gst_element_factory_make("audioconvert", "converter");
if (!m_pipeline.converter) {
ACSDK_ERROR(LX("setupPipelineFailed").d("reason", "createConverterElementFailed"));
@ -561,11 +569,17 @@ bool MediaPlayer::setupPipeline() {
m_busWatchId = gst_bus_add_watch(bus, &MediaPlayer::onBusMessage, this);
gst_object_unref(bus);
// Link only the volume, converter, and sink here. Src will be linked in respective source files.
// Link only the queue, converter, volume, and sink here. Src will be linked in respective source files.
gst_bin_add_many(
GST_BIN(m_pipeline.pipeline), m_pipeline.converter, m_pipeline.volume, m_pipeline.audioSink, nullptr);
GST_BIN(m_pipeline.pipeline),
m_pipeline.decodedQueue,
m_pipeline.converter,
m_pipeline.volume,
m_pipeline.audioSink,
nullptr);
if (!gst_element_link_many(m_pipeline.converter, m_pipeline.volume, m_pipeline.audioSink, nullptr)) {
if (!gst_element_link_many(
m_pipeline.decodedQueue, m_pipeline.converter, m_pipeline.volume, m_pipeline.audioSink, nullptr)) {
ACSDK_ERROR(LX("setupPipelineFailed").d("reason", "createVolumeToConverterToSinkLinkFailed"));
return false;
}
@ -611,6 +625,7 @@ void MediaPlayer::resetPipeline() {
m_pipeline.pipeline = nullptr;
m_pipeline.appsrc = nullptr;
m_pipeline.decoder = nullptr;
m_pipeline.decodedQueue = nullptr;
m_pipeline.converter = nullptr;
m_pipeline.volume = nullptr;
m_pipeline.audioSink = nullptr;
@ -698,8 +713,7 @@ void MediaPlayer::onPadAdded(GstElement* decoder, GstPad* pad, gpointer pointer)
void MediaPlayer::handlePadAdded(std::promise<void>* promise, GstElement* decoder, GstPad* pad) {
ACSDK_DEBUG9(LX("handlePadAddedSignalCalled"));
GstElement* converter = m_pipeline.converter;
gst_element_link(decoder, converter);
gst_element_link(decoder, m_pipeline.decodedQueue);
promise->set_value();
}
@ -907,7 +921,7 @@ void MediaPlayer::handleSetAttachmentReaderSource(
/*
* Once the source pad for the decoder has been added, the decoder emits the pad-added signal. Connect the signal
* to the callback which performs the linking of the decoder source pad to the converter sink pad.
* to the callback which performs the linking of the decoder source pad to decodedQueue sink pad.
*/
if (!g_signal_connect(m_pipeline.decoder, "pad-added", G_CALLBACK(onPadAdded), this)) {
ACSDK_ERROR(LX("handleSetAttachmentReaderSourceFailed").d("reason", "connectPadAddedSignalFailed"));
@ -938,7 +952,7 @@ void MediaPlayer::handleSetIStreamSource(
/*
* Once the source pad for the decoder has been added, the decoder emits the pad-added signal. Connect the signal
* to the callback which performs the linking of the decoder source pad to the converter sink pad.
* to the callback which performs the linking of the decoder source pad to the decodedQueue sink pad.
*/
if (!g_signal_connect(m_pipeline.decoder, "pad-added", G_CALLBACK(onPadAdded), this)) {
ACSDK_ERROR(LX("handleSetIStreamSourceFailed").d("reason", "connectPadAddedSignalFailed"));
@ -966,7 +980,7 @@ void MediaPlayer::handleSetSource(std::string url, std::promise<MediaPlayer::Sou
* The first pad that is added may not be the correct stream (ie may be a video stream), and will fail.
*
* Once the source pad for the decoder has been added, the decoder emits the pad-added signal. Connect the signal
* to the callback which performs the linking of the decoder source pad to the converter sink pad.
* to the callback which performs the linking of the decoder source pad to the decodedQueue sink pad.
*/
if (!g_signal_connect(m_pipeline.decoder, "pad-added", G_CALLBACK(onPadAdded), this)) {
ACSDK_ERROR(LX("handleSetSourceForUrlFailed").d("reason", "connectPadAddedSignalFailed"));

115
README.md
View File

@ -1,67 +1,67 @@
### What is the Alexa Voice Service (AVS)?
The Alexa Voice Service (AVS) enables developers to integrate Alexa directly into their products, bringing the convenience of voice control to any connected device. AVS provides developers with access to a suite of resources to quickly and easily build Alexa-enabled products, including APIs, hardware development kits, software development kits, and documentation.
[Learn more »](https://developer.amazon.com/alexa-voice-service)
### Overview of the AVS Device SDK
The AVS Device SDK provides C++-based (11 or later) libraries that leverage the AVS API to create device software for Alexa-enabled products. It is modular and abstracted, providing components for handling discrete functions such as speech capture, audio processing, and communications, with each component exposing the APIs that you can use and customize for your integration. It also includes a sample app, which demonstrates the interactions with AVS.
### Get Started
You can set up the SDK on the following platforms:
* **Updated** - [Raspberry Pi](https://github.com/alexa/avs-device-sdk/wiki/Raspberry-Pi-Quick-Start-Guide) (Raspbian Stretch)
* **New** - [macOS](https://github.com/alexa/avs-device-sdk/wiki/macOS-Quick-Start-Guide)
* [Linux](https://github.com/alexa/avs-device-sdk/wiki/Linux-Quick-Start-Guide)
You can also prototype with a third party development kit
* [xCORE VocalFusion 4-Mic Kit from XMOS](https://github.com/xmos/vocalfusion-avs-setup)
Or if you prefer, you can start with our [SDK API Documentation](https://alexa.github.io/avs-device-sdk/).
### Learn More About The AVS Device SDK
[Watch this tutorial](https://youtu.be/F5DixCPJYo8) to learn about the how this SDK works and the set up process.
[![Tutorial](https://img.youtube.com/vi/F5DixCPJYo8/0.jpg)](https://www.youtube.com/watch?v=F5DixCPJYo8)
### SDK Architecture
This diagram illustrates the data flows between components that comprise the AVS Device SDK for C++.
![SDK Architecture Diagram](https://m.media-amazon.com/images/G/01/mobile-apps/dex/avs/Alexa_Device_SDK_Architecture.png)
**Audio Signal Processor (ASP)** - Third-party software that applies signal processing algorithms to both input and output audio channels. The applied algorithms are designed to produce clean audio data and include, but are not limited to acoustic echo cancellation (AEC), beam forming (fixed or adaptive), voice activity detection (VAD), and dynamic range compression (DRC). If a multi-microphone array is present, the ASP constructs and outputs a single audio stream for the array.
**Shared Data Stream (SDS)** - A single producer, multi-consumer buffer that allows for the transport of any type of data between a single writer and one or more readers. SDS performs two key tasks:
1. It passes audio data between the audio front end (or Audio Signal Processor), the wake word engine, and the Alexa Communications Library (ACL) before sending to AVS
2. It passes data attachments sent by AVS to specific capability agents via the ACL
SDS is implemented atop a ring buffer on a product-specific memory segment (or user-specified), which allows it to be used for in-process or interprocess communication. Keep in mind, the writer and reader(s) may be in different threads or processes.
**Wake Word Engine (WWE)** - Software that spots wake words in an input stream. It is comprised of two binary interfaces. The first handles wake word spotting (or detection), and the second handles specific wake word models (in this case "Alexa"). Depending on your implementation, the WWE may run on the system on a chip (SOC) or dedicated chip, like a digital signal processor (DSP).
**Audio Input Processor (AIP)** - Handles audio input that is sent to AVS via the ACL. These include on-device microphones, remote microphones, an other audio input sources.
The AIP also includes the logic to switch between different audio input sources. Only one audio input source can be sent to AVS at a given time.
**Alexa Communications Library (ACL)** - Serves as the main communications channel between a client and AVS. The ACL performs two key functions:
1. Establishes and maintains long-lived persistent connections with AVS. ACL adheres to the messaging specification detailed in [Managing an HTTP/2 Connection with AVS](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/docs/managing-an-http-2-connection).
2. Provides message sending and receiving capabilities, which includes support JSON-formatted text, and binary audio content. For additional information, see [Structuring an HTTP/2 Request to AVS](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/docs/avs-http2-requests).
**Alexa Directive Sequencer Library (ADSL)**: Manages the order and sequence of directives from AVS, as detailed in the [AVS Interaction Model](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/interaction-model#channels). This component manages the lifecycle of each directive, and informs the Directive Handler (which may or may not be a Capability Agent) to handle the message.
**Activity Focus Manager Library (AFML)**: Provides centralized management of audiovisual focus for the device. Focus is based on channels, as detailed in the [AVS Interaction Model](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/interaction-model#channels), which are used to govern the prioritization of audiovisual inputs and outputs.
Channels can either be in the foreground or background. At any given time, only one channel can be in the foreground and have focus. If multiple channels are active, you need to respect the following priority order: Dialog > Alerts > Content. When a channel that is in the foreground becomes inactive, the next active channel in the priority order moves into the foreground.
Focus management is not specific to Capability Agents or Directive Handlers, and can be used by non-Alexa related agents as well. This allows all agents using the AFML to have a consistent focus across a device.
**Capability Agents**: Handle Alexa-driven interactions; specifically directives and events. Each capability agent corresponds to a specific interface exposed by the AVS API. These interfaces include:
* [SpeechRecognizer](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speechrecognizer) - The interface for speech capture.
* [SpeechSynthesizer](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speechsynthesizer) - The interface for Alexa speech output.
* [Alerts](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/alerts) - The interface for setting, stopping, and deleting timers and alarms.
@ -71,54 +71,31 @@ Focus management is not specific to Capability Agents or Directive Handlers, and
* [Speaker](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/speaker) - The interface for volume control, including mute and unmute.
* [System](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/system) - The interface for communicating product status/state to AVS.
* [TemplateRuntime](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/reference/templateruntime) - The interface for rendering visual metadata.
### Important Considerations
* Review the AVS [Terms & Agreements](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/support/terms-and-agreements).
* The earcons associated with the sample project are for **prototyping purposes** only. For implementation and design guidance for commercial products, please see [Designing for AVS](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/content/designing-for-the-alexa-voice-service) and [AVS UX Guidelines](https://developer.amazon.com/public/solutions/alexa/alexa-voice-service/content/alexa-voice-service-ux-design-guidelines).
* Please use the contact information below to-
* [Contact Sensory](http://www.sensory.com/support/contact/us-sales/) for information on TrulyHandsFree licensing.
* [Contact KITT.AI](mailto:snowboy@kitt.ai) for information on SnowBoy licensing.
* **IMPORTANT**: The Sensory wake word engine referenced in this document is time-limited: code linked against it will stop working when the library expires. The library included in this repository will, at all times, have an expiration date that is at least 120 days in the future. See [Sensory's GitHub ](https://github.com/Sensory/alexa-rpi#license)page for more information.
### Release Notes and Known Issues
**Note**: Features, updates, and resolved issues from previous releases are available to view in [CHANGELOG.md](https://github.com/alexa/alexa-client-sdk/blob/master/CHANGELOG.md).
v1.2 released 10/27/2017:
v1.2.1 released 11/16/2017:
**Enhancements**
* Updated MediaPlayer to solve stability issues
* All capability agents were refined to work with the updated MediaPlayer
* Added the TemplateRuntime capability agent
* Added the SpeakerManager capability agent
* Added a configuration option ("sampleApp":"endpoint") that allows the endpoint that SampleApp connects to to be specified without changing code or rebuilding
* Added very verbose capture of libcurl debug information
* Added an observer interface to observer audio state changes from AudioPlayer
* Added support for StreamMetadataExtracted Event. Stream tags found in the stream are represented in JSON and sent to AVS
* Added to the SampleApp a simple GuiRenderer as an observer to the TemplateRuntime Capability Agent
* Moved shared libcurl functionality to AVSCommon/Utils
* Added a CMake option to exclude tests from the "make all" build. Use "cmake <absolute-path-to-source>
-DACSDK_EXCLUDE_TEST_FROM_ALL=ON" to enable it. When this option is enabled "make unit" and "make integration" still could be used to build and run the tests
* Added comments to `AlexaClientSDKConfig.json`. These descriptions provide additional guidance for what is expected for each field.
* Enabled pause and resume controls for Pandora.
**Bug Fixes**
* Previously scheduled alerts now play following a restart
* General stability fixes
* Bug fix for CertifiedSender blocking forever if the network goes down while it's trying to send a message to AVS
* Fixes for known issue of Alerts integration tests fail: AlertsTest.UserLongUnrelatedBargeInOnActiveTimer and AlertsTest.handleOneTimerWithVocalStop
* Attempting to end a tap-to-talk interaction with the tap-to-talk button wouldn't work
* SharedDataStream could encounter a race condition due to a combination of a blocking Reader and a Writer closing before writing any data
* Bug-fix for the ordering of notifications within alerts scheduling. This fixes the issue where a local-stop on an alert would also stop a subsequent alert if it were to begin without delay
**Known Issues**
* Capability agent for Notifications is not included in this release
* Inconsistent playback behavior when resuming content ("Alexa, pause." / "Alexa, resume."). Specifically, handling playback offsets, which causes the content to play from the start. This behavior is also occasionally seen with "Next" /
"Previous" as well
* `ACL`'s asynchronous receipt of audio attachments may manage resources poorly in scenarios where attachments are received but not consumed
* Music playback failures may result in an error Text to Speech being rendered repeatedly
* Reminders and named timers don't sound when there is no connection
* GUI cards don't show for Kindle
* Bug fix for [issue #329](https://github.com/alexa/avs-device-sdk/issues/329) - `HTTP2Transport` instances no longer leak when `SERVER_SIDE_DISCONNECT` is encountered.
* Bug fix for [issue #189](https://github.com/alexa/avs-device-sdk/issues/189) - Fixed a race condition in the `Timer` class that sometimes caused `SpeechSynthesizer` to get stuck in the "Speaking" state.
* Bug fix for a race condition that caused `SpeechSynthesizer` to ignore subsequent `Speak` directives.
* Bug fix for corrupted mime attachments.

33
issue_template.md Normal file
View File

@ -0,0 +1,33 @@
**IMPORTANT**: Before you create an issue, please take a look at our [Issue Reporting Guide](https://github.com/alexa/avs-device-sdk/wiki/Issue-Reporting-Guide).
## Briefly summarize your issue:
<!--- Provide a more detailed introduction to the issue itself, and why you consider it to be a bug -->
## What is the expected behavior?
<!--- Tell us what should happen -->
## What behavior are you observing?
<!--- Tell us what's happening instead -->
## Provide the steps to reproduce the issue, if applicable:
<!--- Provide the steps for reproduce -->
## Tell us about your environment:
<!--- Include as many relevant details about the environment you experienced the bug in -->
### What version of the AVS Device SDK are you using?
<x.y.z>
### Tell us what hardware you're using:
- [ ] Desktop / Laptop
- [ ] Raspberry Pi
- [ ] Other - tell us more:
### Tell us about your OS (Type & version):
- [ ] Linux
- [ ] MacOS
- [ ] Raspbian Stretch
- [ ] Raspbian Jessy
- [ ] Other - tell us more:

View File

@ -16,6 +16,7 @@
from flask import Flask, redirect, request
import requests
import json
import commentjson
from os.path import abspath, isfile, dirname
import sys
@ -47,6 +48,9 @@ amazonLwaApiHeaders = {'Content-Type': 'application/x-www-form-urlencoded'}
# Default configuration filename, to be filled by CMake
defaultConfigFilename = "${SDK_CONFIG_FILE_TARGET}"
# Default string for refresh token in configuration file.
defaultRefreshTokenString = "{SDK_CONFIG_REFRESH_TOKEN}"
# JSON keys for config file
CLIENT_ID = 'clientId'
CLIENT_SECRET = 'clientSecret'
@ -71,12 +75,13 @@ if not isfile(configFilename):
try:
configFile = open(configFilename,'r')
except IOError:
print 'File "' + configFilename + '" not found!'
sys.exit(1)
else:
with configFile:
configData = json.load(configFile)
configData = commentjson.load(configFile)
if not configData.has_key(authDelegateKey):
print 'The config file "' + \
configFilename + \
@ -110,6 +115,10 @@ if authDelegateDict.has_key(REFRESH_TOKEN):
amazonLwaApiUrl,
data=urlencode(postData),
headers=amazonLwaApiHeaders)
defaultRefreshTokenString = authDelegateDict[REFRESH_TOKEN];
if defaultRefreshTokenString == "":
print 'Refresh token in the file cannot be empty. Please enter {SDK_CONFIG_REFRESH_TOKEN} in the refresh token'
sys.exit(0)
if 200 == tokenRefreshRequest.status_code:
print 'You have a valid refresh token already in the file.'
sys.exit(0)
@ -159,7 +168,7 @@ def get_refresh_token():
'<br/>' + shutdown()
authDelegateDict['refreshToken'] = tokenRequest.json()['refresh_token']
try:
configFile = open(configFilename,'w')
configFile = open(configFilename,'r')
except IOError:
print 'File "' + configFilename + '" cannot be opened!'
return '<h1>The file "' + \
@ -168,8 +177,15 @@ def get_refresh_token():
shutdown() + \
'</h1>'
else:
with configFile:
json.dump(configData, configFile, indent=4, separators=(',',':'))
return '<h1>The file is written successfully.<br/>' + shutdown() + '</h1>'
fileContent = configFile.read()
if defaultRefreshTokenString not in fileContent:
print '"{defaultRefreshTokenString} not in {configFilename}"'
return '<h1>' + defaultRefreshTokenString + ' string not present in ' + configFilename + \
' please check the file ' + shutdown() + '</h1>'
else:
configFile = open(configFilename,'w')
fileContent = fileContent.replace(defaultRefreshTokenString, tokenRequest.json()['refresh_token'])
configFile.write(fileContent)
return '<h1>The file is written successfully.<br/>' + shutdown() + '</h1>'
app.run(host='127.0.0.1',port='3000')