You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
9.3 KiB
C++
355 lines
9.3 KiB
C++
/*
|
|
* 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);
|
|
}
|
|
|
|
}
|