Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,86 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ArticleDescriptor.h"
#include "Resource.h"
#include <QJsonArray>
#include <QJsonDocument>
using namespace News;
ArticleDescriptor::ArticleDescriptor(
Resource& resource)
: JsonDescriptor(resource)
, m_imageId(m_json["image"].toString())
, m_title(m_json["title"].toString())
, m_body(m_json["body"].toString())
, m_order(m_json["order"].toInt())
{
if (m_json.contains("articleStyle") == true)
{
m_articleStyle = m_json["articleStyle"].toString();
}
}
void ArticleDescriptor::Update() const
{
QJsonObject json;
json["image"] = m_imageId;
json["title"] = m_title;
json["body"] = m_body;
json["order"] = m_order;
json["articleStyle"] = m_articleStyle;
QJsonDocument doc(json);
QByteArray data = doc.toJson(QJsonDocument::Compact).toStdString().data();
m_resource.SetData(data);
}
const QString& ArticleDescriptor::GetArticleStyle() const
{
return m_articleStyle;
}
void ArticleDescriptor::SetArticleStyle(const QString& style)
{
m_articleStyle = style;
}
const QString& ArticleDescriptor::GetImageId() const
{
return m_imageId;
}
void ArticleDescriptor::SetImageId(const QString& imageId)
{
m_imageId = imageId;
}
const QString& ArticleDescriptor::GetTitle() const
{
return m_title;
}
void ArticleDescriptor::SetTitle(const QString& title)
{
m_title = title;
}
const QString& ArticleDescriptor::GetBody() const
{
return m_body;
}
void ArticleDescriptor::SetBody(const QString& body)
{
m_body = body;
}
@@ -0,0 +1,56 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "JsonDescriptor.h"
#include <QJsonObject>
namespace News
{
class Resource;
//! ArticleDescriptor represents Resource as an article
class ArticleDescriptor
: public JsonDescriptor
{
public:
explicit ArticleDescriptor(Resource& resource);
//! If article was modified, call this to update resource data
void Update() const;
const QString& GetArticleStyle() const;
void SetArticleStyle(const QString& style);
const QString& GetImageId() const;
void SetImageId(const QString& imageId);
const QString& GetTitle() const;
void SetTitle(const QString& title);
const QString& GetBody() const;
void SetBody(const QString& body);
private:
QString m_articleStyle = "default";
QString m_imageId;
QString m_title;
QString m_body;
int m_order;
};
}
@@ -0,0 +1,20 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "Descriptor.h"
using namespace News;
Descriptor::Descriptor(Resource& resource)
: m_resource(resource) {}
Descriptor::~Descriptor() {}
@@ -0,0 +1,37 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
namespace News
{
class Resource;
//! Descriptor is a simple solution to add additional functionality to a Resource
/*!
Some descriptors can only work with certain resource types, like AerticleDescriptor
*/
class Descriptor
{
public:
explicit Descriptor(Resource& resource);
virtual ~Descriptor();
Resource& GetResource() const
{
return m_resource;
}
protected:
Resource& m_resource;
};
}
@@ -0,0 +1,25 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "JsonDescriptor.h"
#include "Resource.h"
#include <QJsonDocument>
using namespace News;
JsonDescriptor::JsonDescriptor(Resource& resource)
: Descriptor(resource)
, m_doc(QJsonDocument::fromJson(m_resource.GetData()))
, m_json(m_doc.object())
{
}
@@ -0,0 +1,33 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include "Descriptor.h"
#include <QJsonDocument>
#include <QJsonObject>
namespace News
{
//! JsonDescriptor assumes Resource is a JSON file
class JsonDescriptor
: public Descriptor
{
public:
explicit JsonDescriptor(Resource& resource);
protected:
QJsonDocument m_doc;
QJsonObject m_json;
};
}
@@ -0,0 +1,63 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "QtDownloadManager.h"
#include "QtDownloader.h"
namespace News
{
QtDownloadManager::QtDownloadManager()
: QObject()
, m_worker(new QtDownloader) // this will start the thread which does downloads
{
// make sure the response handlers are queued connections as the worker runs in a different thread
connect(m_worker, &QtDownloader::failed, this, &QtDownloadManager::failedReply, Qt::QueuedConnection);
connect(m_worker, &QtDownloader::successfullyFinished, this, &QtDownloadManager::successfulReply, Qt::QueuedConnection);
}
QtDownloadManager::~QtDownloadManager()
{
// NOTE: we don't delete the QtDownloader; it deletes itself.
// We just tell it to stop
m_worker->Finish();
}
void QtDownloadManager::Download(const QString& url,
std::function<void(QByteArray)> downloadSuccessCallback,
std::function<void()> downloadFailCallback)
{
int downloadId = m_worker->Download(url);
m_downloads[downloadId] = { downloadSuccessCallback, downloadFailCallback };
}
void QtDownloadManager::Abort()
{
m_worker->Abort();
m_downloads.clear();
}
void QtDownloadManager::successfulReply(int downloadId, QByteArray data)
{
m_downloads[downloadId].downloadSuccessCallback(data);
m_downloads.remove(downloadId);
}
void QtDownloadManager::failedReply(int downloadId)
{
m_downloads[downloadId].downloadFailCallback();
m_downloads.remove(downloadId);
}
} // namespace News
#include "NewsShared/ResourceManagement/moc_QtDownloadManager.cpp"
@@ -0,0 +1,62 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <mutex>
#include <functional>
#include <QObject>
#include <QMap>
#endif
namespace News
{
class QtDownloader;
//! QtDownloadManager handles multiple asynchronous downloads
class QtDownloadManager
: public QObject
{
Q_OBJECT
public:
QtDownloadManager();
~QtDownloadManager();
//! Asynchronously download a file from the input url and return it as QByteArray via the success callback
/*!
\param url - file url to download
\param downloadSuccessCallback - if download is successful pass file's data as QByteArray
\param downloadFailCallback - if download failed, pass error message
*/
void Download(const QString& url,
std::function<void(QByteArray)> downloadSuccessCallback,
std::function<void()> downloadFailCallback);
//! Aborts all currently active downloads. Success/failure callbacks will not be called.
void Abort();
private:
void successfulReply(int downloadId, QByteArray data);
void failedReply(int downloadId);
QtDownloader* m_worker = nullptr;
struct DownloadResponses
{
std::function<void(QByteArray)> downloadSuccessCallback;
std::function<void()> downloadFailCallback;
};
QMap<int, DownloadResponses> m_downloads;
};
}
@@ -0,0 +1,138 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "QtDownloader.h"
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QThread>
namespace News
{
QtDownloader::QtDownloader()
: m_thread(new QThread())
{
// make sure that everything that QObject::connects to us knows we're running in a different thread
moveToThread(m_thread);
// handle clean up of both ourselves and of our thread.
// We manage thread clean up so that it can keep running and be cleaned up later, regardless of what
// the thing that created the QtDownloader does
connect(m_thread, &QThread::finished, m_thread, [this] {
m_thread->deleteLater();
deleteLater();
});
auto abortDownloadsHandler = [this] {
auto replies = m_downloads.keys();
for (QNetworkReply* reply : replies)
{
reply->abort();
}
m_downloads.clear();
};
auto queueDownloadHandler = [this](int downloadId, QString url) {
if (m_networkManager)
{
QNetworkReply* reply = m_networkManager->get(QNetworkRequest(QUrl(url)));
m_downloads.insert(reply, downloadId);
}
};
auto createNetworkManagerHandler = [this] {
m_networkManager = new QNetworkAccessManager;
connect(m_networkManager, &QNetworkAccessManager::finished, this, &QtDownloader::downloadFinished);
};
auto deleteNetworkManagerHandler = [this] {
delete m_networkManager;
m_networkManager = nullptr;
};
auto quitHandler = [this] {
// call quit via this callback, so that it executes in the running thread.
// QThread::quit() is actually blocking and waits until everything finishes, so
// we don't want to call it in the main thread
m_thread->quit();
};
// make sure the response handlers are queued connections as the worker runs in one thread, but these triggers
// will be emitted from the main thread
connect(this, &QtDownloader::triggerAbortAll, this, abortDownloadsHandler, Qt::QueuedConnection);
connect(this, &QtDownloader::triggerDownload, this, queueDownloadHandler, Qt::QueuedConnection);
connect(this, &QtDownloader::triggerQuit, this, quitHandler, Qt::QueuedConnection);
// create/delete the QNetworkAccessManager in our thread, to ensure that any slowdowns caused by
// having to create network connectors / load drivers are done in our non-ui thread.
// make sure that these connections are direct so that network requests can't predate the network engine itself
connect(m_thread, &QThread::started, this, createNetworkManagerHandler, Qt::DirectConnection);
connect(m_thread, &QThread::finished, this, deleteNetworkManagerHandler, Qt::DirectConnection);
m_thread->start();
}
QtDownloader::~QtDownloader()
{
}
int QtDownloader::Download(const QString& url)
{
// create a unique id for this download
int downloadId = m_lastId++;
// trigger a download running in our worker thread
Q_EMIT triggerDownload(downloadId, url);
return downloadId;
}
void QtDownloader::Abort()
{
// trigger an abort in our worker thread
Q_EMIT triggerAbortAll();
}
void QtDownloader::Finish()
{
// trigger a quit in our worker thread
Q_EMIT triggerQuit();
}
void QtDownloader::downloadFinished(QNetworkReply* reply)
{
// Note: this will run in our worker thread
int downloadId = m_downloads[reply];
// emit the signal back to the main thread indicating that we're finished, either
// successfully or unsuccessfully
if (reply->error() == QNetworkReply::NoError)
{
Q_EMIT successfullyFinished(downloadId, reply->readAll());
}
else
{
Q_EMIT failed(downloadId);
}
// clean up the reply; have to do this later, according to the Qt docs
reply->deleteLater();
// make sure to remove our reference to this reply from our list of active downloads
m_downloads.remove(reply);
}
#include "NewsShared/ResourceManagement/moc_QtDownloader.cpp"
}
@@ -0,0 +1,71 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <mutex>
#include <functional>
#include <QObject>
#include <QMap>
#endif
class QNetworkAccessManager;
class QNetworkReply;
class QThread;
namespace News
{
//! QtDownloader is a wrapper around Qt's file download functions
/*!
The QtDownloader spins up another thread and does all downloads in that thread.
The public slot methods (Finish, Download and Abort) can all be called from any thread.
The response signals (successfullyFinished and failed) should be QObject::connect to with
Qt::QueuedConnection, as they will be emitted from the worker thread.
*/
class QtDownloader
: public QObject
{
Q_OBJECT
public:
QtDownloader();
~QtDownloader();
public Q_SLOTS:
void Finish();
int Download(const QString& url);
void Abort();
Q_SIGNALS:
void successfullyFinished(int downloadId, QByteArray data);
void failed(int downloadId);
// ***********************************************
// private - DO NOT CONNECT TO outside of the class!
// (qt signals can't be made private)
void triggerAbortAll();
void triggerDownload(int downloadId, QString url);
void triggerQuit();
// ***********************************************
private:
void downloadFinished(QNetworkReply* reply);
int m_lastId = 0;
QMap<QNetworkReply*, int> m_downloads;
QNetworkAccessManager* m_networkManager = nullptr;
QThread* m_thread;
};
}
@@ -0,0 +1,102 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "Resource.h"
#include <QJsonObject>
using namespace News;
Resource::Resource(const QJsonObject& json)
: Resource(
json["id"].toString(),
QByteArray(),
json["url"].toString(),
json["type"].toString(),
json["refCount"].toInt(),
json["version"].toInt()) {}
Resource::Resource(const QString& id, const QString& type)
: Resource(
id,
QByteArray(),
"",
type,
1,
0) {}
Resource::Resource(const QString& id,
const QByteArray& data,
[[maybe_unused]] const QString& url,
const QString& type,
int refCount,
int version)
: m_id(id)
, m_data(data)
, m_type(type)
, m_refCount(refCount)
, m_version(version) {}
Resource::~Resource() {}
void Resource::Write(QJsonObject& json) const
{
json["id"] = m_id;
json["type"] = m_type;
json["refCount"] = m_refCount;
json["version"] = m_version;
}
QString Resource::GetId() const
{
return m_id;
}
void Resource::SetId(const QString& id)
{
m_id = id;
}
QByteArray Resource::GetData() const
{
return m_data;
}
void Resource::SetData(QByteArray data)
{
m_data = data;
}
QString Resource::GetType() const
{
return m_type;
}
int Resource::GetRefCount() const
{
return m_refCount;
}
void Resource::SetRefCount(int refCount)
{
m_refCount = refCount;
}
int Resource::GetVersion() const
{
return m_version;
}
void Resource::SetVersion(int version)
{
m_version = version;
}
@@ -0,0 +1,73 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <QString>
class QJsonObject;
namespace News
{
class Descriptor;
//! Resource is a central element of in-editor messages
//! It represents articles, images, and anything else that is part of news feed
class Resource
{
public:
//! resources are stored as json objects in \ref News::ResourceManifest
//! this creates resource with empty data array, that can be downloaded later
//! by calling News::ResourceManifest::Sync
explicit Resource(const QJsonObject& json);
explicit Resource(const QString& id,
const QString& type);
Resource(const QString& id,
const QByteArray& data,
const QString& url,
const QString& type,
int refCount,
int version);
~Resource();
//! Saves resource's description to a json file
void Write(QJsonObject& json) const;
QString GetId() const;
void SetId(const QString& id);
QByteArray GetData() const;
void SetData(QByteArray data);
QString GetType() const;
int GetRefCount() const;
void SetRefCount(int refCount);
int GetVersion() const;
void SetVersion(int version);
private:
QString m_id;
QByteArray m_data;
QString m_type;
int m_refCount;
int m_version;
};
}
@@ -0,0 +1,354 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include "ResourceManifest.h"
#include "NewsShared/ResourceManagement/QtDownloadManager.h"
#include "NewsShared/ResourceManagement/Resource.h"
#include "NewsShared/ResourceManagement/ArticleDescriptor.h"
#include <QJsonArray>
#include <QByteArray>
#include <QFile>
#include <QTextStream>
#include <QCoreApplication>
namespace News
{
const QString ResourceManifest::MANIFEST_NAME = "resourceManifest";
bool ResourceManifest::s_syncing = false;
ResourceManifest::ResourceManifest(
std::function<void()> syncSuccessCallback,
std::function<void(ErrorCode)> syncFailCallback,
std::function<void(QString, LogType)> syncUpdateCallback)
: m_downloader(new QtDownloadManager)
, m_syncSuccessCallback(syncSuccessCallback)
, m_syncFailCallback(syncFailCallback)
, m_syncUpdateCallback(syncUpdateCallback)
{
}
ResourceManifest::~ResourceManifest()
{
// clean everything up
DeleteResources();
delete m_downloader;
}
Resource* ResourceManifest::FindById(const QString& id) const
{
return FindById(id, m_resources);
}
Resource* ResourceManifest::FindById(const QString& id, const QList<Resource*>& resources)
{
auto it = std::find_if(
resources.begin(),
resources.end(),
[id](Resource* resource) -> bool
{
return resource->GetId().compare(id) == 0;
});
if (it == resources.end())
{
return nullptr;
}
return *it;
}
Resource* ResourceManifest::FindById(const QString& id,
const QStack<Resource*>& resources)
{
auto it = std::find_if(
resources.begin(),
resources.end(),
[id](Resource* resource) -> bool
{
return resource->GetId().compare(id) == 0;
});
if (it == resources.end())
{
return nullptr;
}
return *it;
}
void ResourceManifest::Sync()
{
if (s_syncing)
{
FailSync(ErrorCode::AlreadySyncing);
return;
}
s_syncing = true;
m_failed = false;
m_syncUpdateCallback("Starting sync", LogInfo);
ReadConfig();
// first download the manifest json
m_syncUpdateCallback("Downloading manifest", LogInfo);
m_downloader->Download(QString(m_url).append(MANIFEST_NAME),
std::bind(&ResourceManifest::OnDownloadSuccess, this, std::placeholders::_1),
std::bind(&ResourceManifest::OnDownloadFail, this));
}
void ResourceManifest::Abort()
{
m_aborted = true;
m_downloader->Abort();
}
void ResourceManifest::Reset()
{
if (s_syncing)
{
m_syncUpdateCallback("Sync is already running", LogError);
return;
}
m_aborted = false;
m_failed = false;
m_version = -1;
DeleteResources();
m_order.clear();
}
QList<Resource*>::const_iterator ResourceManifest::begin() const
{
return m_resources.constBegin();
}
QList<Resource*>::const_iterator ResourceManifest::end() const
{
return m_resources.constEnd();
}
QList<QString> ResourceManifest::GetOrder() const
{
return m_order;
}
void ResourceManifest::OnDownloadSuccess(QByteArray data)
{
QJsonDocument doc(QJsonDocument::fromJson(data));
if (doc.isNull())
{
FailSync(ErrorCode::FailedToParseManifest);
return;
}
ErrorCode error = Read(doc.object());
if (error != ErrorCode::None)
{
FailSync(error);
return;
}
// check how many resources to sync
PrepareForSync();
// if there is anything to sync, do that
if (m_syncLeft > 0)
{
m_syncUpdateCallback("Syncing resources", LogInfo);
SyncResources();
}
// otherwise just finish sync
else
{
m_syncUpdateCallback("No new resources to sync", LogInfo);
FinishSync();
}
}
void ResourceManifest::OnDownloadFail()
{
FailSync(ErrorCode::ManifestDownloadFail);
}
ErrorCode ResourceManifest::Read(const QJsonObject& json)
{
m_version = json["version"].toInt();
QJsonArray resourceArray = json["resources"].toArray();
// initially mark ALL existing resource for deletion
QList<Resource*> toDelete = m_resources;
for (auto resourceDoc : resourceArray)
{
auto pNewResource = new Resource(resourceDoc.toObject());
// find local resource with the same id as new resource
auto pOldResource = FindById(pNewResource->GetId(), m_resources);
// if resource with the same id already exists then check its version
if (pOldResource)
{
// local resource is outdated, keep it in delete list, and download new one instead
if (pNewResource->GetVersion() > pOldResource->GetVersion())
{
m_toDownload.push(pNewResource);
}
// local resource is newer or same version, keep it (remove from toDelete list)
// and don't need to download new one
else
{
delete pNewResource;
toDelete.removeAll(pOldResource);
}
}
// resource with same id not found
else
{
m_toDownload.push(pNewResource);
}
}
// delete everything that's not in s3
for (auto pResource : toDelete)
{
RemoveResource(pResource);
delete pResource;
}
// parse order of articles
m_order.clear();
QJsonArray orderArray = json["order"].toArray();
for (auto idObject : orderArray)
{
m_order.append(idObject.toString());
}
return ErrorCode::None;
}
void ResourceManifest::PrepareForSync()
{
if (m_aborted)
{
m_syncLeft = 0;
}
m_syncLeft = m_toDownload.count();
}
void ResourceManifest::SyncResources()
{
DownloadResources();
}
void ResourceManifest::DownloadResources()
{
while (m_toDownload.count() > 0)
{
m_syncUpdateCallback(
QString("Downloading: %1 resources left").arg(m_toDownload.count()),
LogInfo);
auto pResource = m_toDownload.pop();
m_downloader->Download(QString(m_url).append(pResource->GetId()),
//download success
[&, pResource](QByteArray data)
{
pResource->SetData(data);
AppendResource(pResource);
UpdateSync();
},
//download fail
[&, pResource]()
{
m_failed = true;
delete pResource;
m_syncUpdateCallback("Failed to download resource", LogError);
UpdateSync();
});
}
}
void ResourceManifest::ReadConfig()
{
QFile file(QCoreApplication::applicationDirPath() + "/newsConfig.txt");
if (file.exists())
{
if (file.open(QIODevice::ReadOnly))
{
QTextStream in(&file);
m_url = in.readAll().trimmed();
file.close();
}
}
}
void ResourceManifest::DeleteResources()
{
for (auto pResource : m_toDownload)
{
delete pResource;
}
m_toDownload.clear();
for (auto pResource : m_resources)
{
delete pResource;
}
m_resources.clear();
}
void ResourceManifest::UpdateSync()
{
m_syncLeft--;
if (m_syncLeft == 0)
{
if (!m_failed)
{
FinishSync();
}
else
{
FailSync(ErrorCode::FailedToSync);
}
}
}
void ResourceManifest::FinishSync()
{
if (!m_failed)
{
m_syncSuccessCallback();
}
else
{
m_syncFailCallback(m_errorCode);
}
s_syncing = false;
}
void ResourceManifest::FailSync(ErrorCode error)
{
m_failed = true;
m_errorCode = error;
FinishSync();
}
void ResourceManifest::AppendResource(Resource* pResource)
{
m_resources.append(pResource);
}
void ResourceManifest::RemoveResource(Resource* pResource)
{
m_resources.removeAll(pResource);
}
}
@@ -0,0 +1,139 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once
#include <QList>
#include <QStack>
#include <functional>
#include "NewsShared/LogType.h"
#include "NewsShared/ErrorCodes.h"
class QJsonObject;
namespace News
{
class ArticleDescriptor;
class UidGenerator;
class S3Connector;
class QtDownloadManager;
class Descriptor;
class DownloadDescriptor;
class Resource;
//! ResourceManifest manages resources.
/*!
Manifest contains information on resources, it handles syncing resources with s3
*/
class ResourceManifest
{
public:
//! ResourceManifest ctor
/*!
\param syncSuccessCallback - called once when everything is synced
\param syncFailCallback - called once when sync failed
\param syncUpdateCallback - called multiple times to update information on sync process
*/
explicit ResourceManifest(
std::function<void()> syncSuccessCallback,
std::function<void(ErrorCode)> syncFailCallback,
std::function<void(QString, LogType)> syncUpdateCallback);
virtual ~ResourceManifest();
//! Find a resource that matches id
/*!
\retval Resource * - a pointer to a Resource with matching id, if none found return nullptr
*/
Resource* FindById(const QString& id) const;
static Resource* FindById(const QString& id, const QList<Resource*>& resources);
static Resource* FindById(const QString& id, const QStack<Resource*>& resources);
//! Sync resources with s3
/*
1) First download resource manifest file
2) Parse manifest
3) Determine which resources need to be downloaded, updated, or deleted
4) Download missing resources or resource that are out of date
5) Call m_syncSuccessCallback
*/
virtual void Sync();
//! Gracegully stop sync process
/*!
Aborting works differently depending at what point during sync porocess it is called
If called before resources started to download, then skip download altogether
Otherwise gracefully abort all downloads and call m_syncFailCallback
*/
void Abort();
//! Called when switching endpoints to reset resource manifest to a clean state
virtual void Reset();
QList<Resource*>::const_iterator begin() const;
QList<Resource*>::const_iterator end() const;
//! Get order of article resources, so they can be displayed properly in ArticleViewContainer
QList<QString> GetOrder() const;
protected:
//! The root location of cloudfront resources
QString m_url = "https://lumberyard-data.amazon.com/";
//! Name of resourceManifest file that links all other resources
static const QString MANIFEST_NAME;
//! Identifies whether syncing is in progress
static bool s_syncing;
//! Manifest Version
int m_version = -1;
//! Number of resources left to sync
int m_syncLeft = 0;
//! Identifies whether sync process was aborted
bool m_aborted = false;
//! Indentifies whether sync process has failed
bool m_failed = false;
ErrorCode m_errorCode = ErrorCode::None;
QtDownloadManager* m_downloader = nullptr;
QList<Resource*> m_resources;
QList<QString> m_order;
QStack<Resource*> m_toDownload;
std::function<void()> m_syncSuccessCallback;
std::function<void(ErrorCode)> m_syncFailCallback;
std::function<void(QString, LogType)> m_syncUpdateCallback;
//! Parse resource manifest json, and figure out which resources need to be downloaded
virtual ErrorCode Read(const QJsonObject& json);
//! Executed before sync to figure out how many resources need to be synced
virtual void PrepareForSync();
//! Actual sync function
virtual void SyncResources();
//! Check whether everything is synced, if so call ResourceManifest::FinishSync
void UpdateSync();
//! Notify that everything is synced
virtual void FinishSync();
void FailSync(ErrorCode error);
virtual void AppendResource(Resource* pResource);
virtual void RemoveResource(Resource* pResource);
virtual void OnDownloadSuccess(QByteArray data);
virtual void OnDownloadFail();
virtual void DownloadResources();
private:
void ReadConfig();
void DeleteResources();
};
} // namespace News