Feature/assets (#1784)

General overhaul of the Asset loading system
This commit is contained in:
Alexander Bock
2021-12-19 21:04:01 +04:00
committed by GitHub
parent f8b5d4b662
commit debcb43ade
167 changed files with 3251 additions and 4441 deletions
+370 -195
View File
@@ -28,267 +28,442 @@
#include <ghoul/misc/boolean.h>
#include <atomic>
#include <condition_variable>
#include <filesystem>
#include <fstream>
#include <functional>
#include <optional>
#include <string>
#include <thread>
#include <vector>
// @TODO: This class is a diamond-of-death; maybe some redesign to make the things into
// components, rather than inheritance?
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4250)
#endif
#include <chrono>
namespace openspace {
namespace curlfunctions {
size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userData);
int progressCallback(void* userData, int64_t nTotalDownloadBytes,
int64_t nDownloadedBytes, int64_t nTotalUploadBytes, int64_t nUploadBytes);
size_t headerCallback(char* ptr, size_t size, size_t nmemb, void* userData);
}
// Synchronous http request
/**
* This class performs a synchronous HTTP request to the provided URL. Any result that is
* returned based on this request is returned through three callback functions that can be
* registered using the #onHeader, #onProgress, and #onData functions. Calling these
* functions will overwrite any previously registered handler.
* The ProgressCallback can be used to stop the download if the handler returns \c false.
*
* The workflow for this class:
* 1. Create a new object with the URL that points to the location from which the data
* should be loaded
* 2. Register the callbacks that are needed (at least the #onData callback)
* 3. Start the download with the #perform function
*/
class HttpRequest {
public:
enum class ReadyState : unsigned int {
Unsent,
Loading,
ReceivedHeader,
Fail,
Success
};
/**
* This callback is called every time there is progress to report for the download. It
* transmits currently downloaded number of bytes (which can be 0 if the download
* hasn't started yet) and the total number of bytes if that value is known. The
* return value determines if the download should continue (if the function returns
* \c true) or if it should abort (if \c false is returned).
*
* \param downloadedBytes The number of bytes that have been downloaded thus far or 0
* if the download hasn't started yet
* \param totalBytes The total number of bytes for the download if it is known, or a
* std::nullopt of the total size is unknown
* \return \c true if the download should continue or \c false if it should be aborted
*/
using ProgressCallback = std::function<
bool(size_t downloadedBytes, std::optional<size_t> totalBytes)
>;
struct Progress {
bool totalBytesKnown = false;
size_t totalBytes = 0;
size_t downloadedBytes = 0;
};
/**
* This callback is executed every time a chunk of data arrived for the download. The
* parameters point to the buffer of this new incoming data and the number of bytes
* that have arrived. The buffer pointed to will most likely only be valid during the
* time of the callback and it is the callbacks responsibility to store the contents
* of the buffer before the callback returns. If the return value is \c true, the
* download continues, if it is \c false, this signals to the library that an error
* has occurred from which recovery is not possible.
*
* \param buffer The pointer to the beginning of the buffer where the new incoming
* data is located. This buffer is only valid during the execution of this
* callback and the contents of the buffer should be copied to a different
* location
* \param size The number of bytes that are contained in the \p buffer
* \return \c true if the download should continue or \c false if it should be aborted
*/
using DataCallback = std::function<bool(char* buffer, size_t size)>;
struct Data {
char* buffer;
size_t size;
};
/**
* This callback is executed when the header of an HTTP request is received
* successfully. The provided buffer and size contain the contents of the header field
* for the response. The buffer pointed to will most likely only be valid during the
* time of the callback and it is the callbacks responsibility to store the contents
* of the buffer before the callback returns. If the return value is \c true, the
* download continues, if it is \c false, this signals to the library that an error
* has occurred from which recovery is not possible. If this function returns
* \c false, it will cause the main download to not start at all.
*
* \param buffer The pointer to the beginning of the buffer where the header
* information is located. This buffer is only valid during the execution of
* this callback and the contents of the buffer should be copied to a different
* location
* \param size The number of bytes that are contained in the \p buffer
* \return \c true if the download should continue or \c false if it should be aborted
*/
using HeaderCallback = std::function<bool(char* buffer, size_t size)>;
struct Header {
char* buffer;
size_t size;
};
/**
* Creates a new HttpRequest object that will try to download the contents at the
* provided \p url.
*
* \param url The URL that should be requested by this HttpRequest
*
* \pre \p url must not be empty
*/
explicit HttpRequest(std::string url);
using ReadyStateChangeCallback = std::function<void(ReadyState)>;
// ProgressCallback: Return non-zero value to cancel download.
using ProgressCallback = std::function<int(Progress)>;
// DataCallback: Return number of bytes successfully stored.
// If this does not match the data buffer sice reported in Data,
// the request will fail.
using DataCallback = std::function<size_t(Data)>;
// HeaderCallback: Return number of bytes successfully stored.
// If this does not match the data buffer sice reported in Header,
// the request will fail.
using HeaderCallback = std::function<size_t(Header)>;
struct RequestOptions {
int requestTimeoutSeconds; // 0 for no timeout
};
HttpRequest(std::string url);
void onReadyStateChange(ReadyStateChangeCallback cb);
/**
* Registers a callback that will be called when the header for the request has been
* transmitted successfully. The contents of the header will be passed into the
* callback and the callback returns whether the request should proceed or be aborted.
*
* \param cb The callback that should be registered. This will silently replace any
* previously registered callback
*/
void onHeader(HeaderCallback cb);
/**
* Registers a callback that will be called whenever there is progress to be reported
* on the transfer of the request's body. The callback will receive information about
* the number of bytes that have been downloaded and the total number of bytes that
* should be downloaded to complete the request. This information might not always be
* available. The callback's return value determines whether the request should
* continue or be aborted.
*
* \param cb The callback that should be registered. This will silently replace any
* previously registered callback
*/
void onProgress(ProgressCallback cb);
/**
* Registers a callback that will be called whenever there is new data that has been
* received from the request. It is this callback's responsibility to store that data
* in a place that is persistent across multiple calls to the callback, usually by
* storing it in an external buffer and appending to it. The callback can return
* whether the download should proceed (by returning \c true) or be aborted (by
* returning \c false).
*
* \param cb The callback that should be registered. This will silently replace any
* previously registered callback
*/
void onData(DataCallback cb);
void perform(RequestOptions opt);
/**
* Performs the request to the URL provided in the constructor. As this request is
* handled synchronously, this function will only return once the request has been
* completed successfully or failed. During this call, the registered callbacks will
* be called repeatedly until the request finishes. This function returns whether the
* request was completed successfully or failed.
*
* \param timeout The amount of time the request will wait before aborting due to the
* server not responding. If this value is 0, there is no timeout on the
* request.
*
* \return \c true if the request completed successfully, \c false otherwise
*/
bool perform(std::chrono::milliseconds timeout = std::chrono::milliseconds(0));
/**
* Returns the URL that was passed into the constructor of this HttpRequest.
*
* \return The URL that was passed into the constructor of this HttpRequest
*/
const std::string& url() const;
private:
void setReadyState(ReadyState state);
std::string _url;
ReadyStateChangeCallback _onReadyStateChange;
ProgressCallback _onProgress;
DataCallback _onData;
/// The callback that will be called when the header was received successfully
HeaderCallback _onHeader;
ReadyState _readyState;
Progress _progress;
/// The callback that will be called when there is progress to be reported
ProgressCallback _onProgress;
friend size_t curlfunctions::writeCallback(char* ptr, size_t size, size_t nmemb,
void* userData);
/// The callback that will be called when there is data to be stored away
DataCallback _onData;
friend int curlfunctions::progressCallback(void* userData,
int64_t nTotalDownloadBytes,
int64_t nDownloadedBytes, int64_t nTotalUploadBytes, int64_t nUploadBytes);
friend size_t curlfunctions::headerCallback(char* ptr, size_t size, size_t nmemb,
void* userData);
/// The URL that this HttpRequest is going to request
std::string _url;
};
/**
* This abstract base class uses the HttpRequest class to perform an asynchronous
* download. Every subclass needs to implement at least the #handleData function that will
* be called every time a chunk of data has been received from the request. The download
* is started through the #start function and it is possible to turn this into a
* synchronous download by executing the #wait function directly afterwards. If a
* HttpRequest::ProgressCallback has been registered through the #onProgress function,
* that callback is called every time the download some progress to report.
*/
class HttpDownload {
public:
using ProgressCallback = std::function<bool(HttpRequest::Progress)>;
using HeaderCallback = std::function<bool(HttpRequest::Header)>;
/**
* Creates a new HttpDownload that will start to download the file pointed to by the
* \param url parameter as soon as the download is #start ed.
*
* \param url The URL that should be downloaded by this HttpDownload
*
* \pre \p url must not be empty
*/
explicit HttpDownload(std::string url);
HttpDownload();
HttpDownload(HttpDownload&& d) = default;
HttpDownload& operator=(HttpDownload&&) = default;
virtual ~HttpDownload() = default;
void onProgress(ProgressCallback progressCallback);
void onHeader(HeaderCallback headerCallback);
bool hasStarted() const;
/**
* Virtual destructor that will cancel the ongoing download and block until the
* download is successfully canceled.
*/
virtual ~HttpDownload();
/**
* Registers a callback that will be called whenever there is progress to be reported
* on the file's download. The callback will receive information about the number of
* bytes that have been downloaded and the total number of bytes that should be
* downloaded. This information might not always be available. The callback's return
* value determines whether the request should continue or be aborted.
*
* \param progressCallback The callback that should be registered. This will silently
* replace any previously registered callback
*/
void onProgress(HttpRequest::ProgressCallback progressCallback);
/**
* Starts the asynchronous download of the file by starting a new thread that will
* take care of the download, meaning that this function will return almost
* instantaneously. If the HttpDownload is already downloading a file this function
* does nothing.
*
* \param timeout The number of milliseconds that the download will be kept alive
* while waiting for a reply from the server. If this value is 0, the
* connection will never time out
*/
void start(std::chrono::milliseconds timeout = std::chrono::milliseconds(0));
/**
* Cancels the ongoing download. Because of the underlying library that is used, the
* transfer will only be aborted the next time any piece of data is received or the
* library reports any progress.
*/
void cancel();
/**
* This function will wait until the download has completed and will return the
* success of the download back to the caller.
*
* \return \c true if the downloaded succeeded or \c false if the download failed
*/
bool wait();
/**
* Returns \c true if the download has completed and it failed, or \c false if either
* the download is till ongoing or is finished and has succeeded.
*
* \return Whether the download has completed and it failed
*/
bool hasFailed() const;
/**
* Returns \c true if the download has completed successfully , or \c false if either
* the download is till ongoing or is finished and has failed.
*
* \return Whether the download has completed successfully
*/
bool hasSucceeded() const;
protected:
virtual size_t handleData(HttpRequest::Data d) = 0;
virtual bool initDownload() = 0;
virtual bool deinitDownload() = 0;
bool callOnProgress(HttpRequest::Progress p);
void markAsStarted();
void markAsSuccessful();
void markAsFailed();
private:
ProgressCallback _onProgress;
bool _started = false;
bool _failed = false;
bool _successful = false;
};
class SyncHttpDownload : public virtual HttpDownload {
public:
SyncHttpDownload(std::string url);
SyncHttpDownload(SyncHttpDownload&& d) = default;
SyncHttpDownload& operator=(SyncHttpDownload&&) = default;
virtual ~SyncHttpDownload() = default;
void download(HttpRequest::RequestOptions opt);
/**
* Returns the URL that was passed into the constructor of this HttpDownload.
*
* \return The URL that was passed into the constructor of this HttpDownload
*/
const std::string& url() const;
protected:
HttpRequest _httpRequest;
};
/**
* This abstract function has to be implemented by any concrete subclass to handle an
* incoming chunk of data from the download. The parameters point to the \p buffer of
* this new incoming data and the number of bytes that have arrived. The buffer
* pointed to will most likely only be valid during the time of the callback and it is
* the callbacks responsibility to store the contents of the buffer before the
* callback returns. If the return value is \c true, the download continues, if it is
* \c false, this signals to the library that an error has occurred from which
* recovery is not possible. This function will be called on a different thread from
* the one that called the #start method.
*
* \param buffer The beginning of the buffer of this chunk of data
* \param size The number of bytes that the \p buffer contains
*
* \return The implementation should return \c true if the downloading should continue
* and \c false if the handling of the data caused some error that the
* subclass is incapable of recovering from
*/
virtual bool handleData(char* buffer, size_t size) = 0;
class AsyncHttpDownload : public virtual HttpDownload {
public:
AsyncHttpDownload(std::string url);
AsyncHttpDownload(AsyncHttpDownload&& d);
virtual ~AsyncHttpDownload() = default;
void start(HttpRequest::RequestOptions opt);
void cancel();
void wait();
/**
* This function is called before the downloading starts and can be used by subclasses
* to perform one-time setup functions, such as opening a file, reserving a block of
* storage, etc. This function guaranteed to be only called once per HttpDownload.
* The return value determines if the setup operation completed successfully or if an
* error occurred that will cause the download to be terminated. This function will be
* called on a different thread from the one that called the #start method.
*
* \return \c true if the setup completed successfully and \c false if the setup
* failed unrecoverably
*/
virtual bool setup();
const std::string& url() const;
protected:
void download(HttpRequest::RequestOptions opt);
/**
* This function is called after the downloading has finished and before a potential
* call to #wait is performed. This function can be used by a subclass to perform
* one-time operations that are required when the downloading fininshes, such as
* closing file handles, committing some memory etc. The return value of this function
* signals whether the teardown completed successfully. This function will be called
* on a different thread from the one that called the #start method.
*
* \return \c true if the teardown completed successfully and \c false if it failed
*/
virtual bool teardown();
private:
HttpRequest _httpRequest;
std::thread _downloadThread;
std::mutex _conditionMutex;
std::condition_variable _downloadFinishCondition;
/// The callback that will be called whenever there is some progress to be reported
HttpRequest::ProgressCallback _onProgress;
/// Value indicating whether the HttpDownload is currently downloading a file
bool _isDownloading = false;
/// Value indicating whether the download is finished
bool _isFinished = false;
/// Value indicated whether the download was successful
bool _isSuccessful = false;
/// Marker telling the downloading thread that the download should be cancelled
bool _shouldCancel = false;
std::mutex _stateChangeMutex;
/// The HttpRequest class that will be used for the download
HttpRequest _httpRequest;
/// The thread that contains the HttpRequest to download the file
std::thread _downloadThread;
/// This condition variable is used by the #wait function to be able to wait for
/// completion of the downloading thread
std::condition_variable _downloadFinishCondition;
};
class HttpFileDownload : public virtual HttpDownload {
/**
* This specific subclass of the HttpDownload downloads the contents of the provided URL
* into a file on disk. By default, an existing file will not be overwritten and will
* cause the download to fail. This behavior can be overwritten through a parameter in the
* constructor of this class.
*/
class HttpFileDownload : public HttpDownload {
public:
BooleanType(Overwrite);
HttpFileDownload() = default;
HttpFileDownload(std::string destination, Overwrite = Overwrite::No);
/**
* Constructor that will create a HttpFileDownload which will download the contents of
* the provided \p url to the \p destinationPath. If the \p destinationPath already
* contains a file and \p overwrite is Overwrite::No, the download will fail; if it is
* Overwrite::Yes, the existing content at the \p destinationPath will be overwritten.
*/
HttpFileDownload(std::string url, std::filesystem::path destinationPath,
Overwrite overwrite = Overwrite::No);
/**
* This destructor will cancel any ongoing download and wait for its completion, so it
* might not block for a short amount of time.
*/
virtual ~HttpFileDownload() = default;
const std::string& destination() const;
/**
* Returns the path where the contents of the URL provided in the constructor will be
* saved to.
*
* \return The path where URL will be downloaded to
*/
std::filesystem::path destination() const;
protected:
bool initDownload() override;
bool deinitDownload() override;
size_t handleData(HttpRequest::Data d) override;
private:
/// Will create all directories that are necessary to reach _destination and then
/// fight with other HttpFileDownloads to get one of a limited number of file handles
/// to open _file
bool setup() override;
static std::mutex _directoryCreationMutex;
/// Closes the _file and returns the handle back to the pool of available handles
bool teardown() override;
/// Stores the chunk of data into the _file handle
bool handleData(char* buffer, size_t size) override;
/// A flag whether this HttpFileDownload got a handle from the limited supply of
/// handles. This limit is used to prevent all HttpFileDownloads from getting file
/// handles from the operating system as that resource is limited and downloads would
/// fail unrecoverably if no handles are available. So we limit the maximum number and
/// if that number is exceeded, the HttpFileDownload will wait until a handle is
/// available.
std::atomic_bool _hasHandle = false;
private:
std::string _destination;
bool _overwrite;
/// The destination path where the contents of the URL provided in the constructor
/// will be saved to
std::filesystem::path _destination;
/// The file handle to the _destination used to save incoming chunks
std::ofstream _file;
static const int MaxFilehandles = 35;
static std::atomic_int nCurrentFilehandles;
/// Mutex that will be prevent multiple HttpFileDownloads to simultaneously try to
/// create the necessary intermediate directories, which would cause issues
static std::mutex _directoryCreationMutex;
/// The maximum number of file handles that all HttpFileDownloads combined can use up
static constexpr const int MaxFileHandles = 32;
/// Stores the number of currently open file handles across all HttpFileDownloads
static std::atomic_int nCurrentFileHandles;
};
class HttpMemoryDownload : public virtual HttpDownload {
/**
* This concerete HttpDownload subclass downloads the contents of the URL passed into the
* constructor into a buffer of memory that can be retrieve. Please note that that buffer
* should only be used accessed once the HttpDownload::hasFinished function returns
* \c true.
*/
class HttpMemoryDownload : public HttpDownload {
public:
HttpMemoryDownload() = default;
HttpMemoryDownload(HttpMemoryDownload&& d) = default;
HttpMemoryDownload& operator=(HttpMemoryDownload&&) = default;
/**
* Creates an instance of a HttpMemoryDownload that will download the contents of the
* \p url into memory
*
* \param url The URL whose contents should be downloaded
*/
explicit HttpMemoryDownload(std::string url);
/**
* This destructor will cancel any ongoing download and wait for its completion, so it
* might not block for a short amount of time.
*/
virtual ~HttpMemoryDownload() = default;
/**
* Returns a reference to the buffer that is used to store the contents of the URL
* passed in the constructor. Please observe that while the HttpDownload::hasFinished
* method returns \c false, this buffer will be changed by a different thread and
* access is not thread-safe. After that function returns \c true, it is safe to
* access the buffer.
*
* \return A reference to the buffer used to hold the contents of the URL
*/
const std::vector<char>& downloadedData() const;
protected:
bool initDownload() override;
bool deinitDownload() override;
size_t handleData(HttpRequest::Data d) override;
private:
std::vector<char> _downloadedData;
};
/// Stores each downloaded chunk into the stored buffer
bool handleData(char* buffer, size_t size) override;
// Synchronous download to memory
class SyncHttpMemoryDownload : public SyncHttpDownload, public HttpMemoryDownload {
public:
SyncHttpMemoryDownload(std::string url);
virtual ~SyncHttpMemoryDownload() = default;
};
// Synchronous download to file
class SyncHttpFileDownload : public SyncHttpDownload, public HttpFileDownload {
public:
SyncHttpFileDownload(
std::string url,
std::string destinationPath,
HttpFileDownload::Overwrite = Overwrite::No
);
virtual ~SyncHttpFileDownload() = default;
};
// Asynchronous download to memory
class AsyncHttpMemoryDownload : public AsyncHttpDownload, public HttpMemoryDownload {
public:
AsyncHttpMemoryDownload(std::string url);
virtual ~AsyncHttpMemoryDownload() = default;
};
// Asynchronous download to file
class AsyncHttpFileDownload : public AsyncHttpDownload, public HttpFileDownload {
public:
AsyncHttpFileDownload(
std::string url,
std::string destinationPath,
HttpFileDownload::Overwrite = Overwrite::No
);
virtual ~AsyncHttpFileDownload() = default;
/// The buffer where the downloaded chunks are accumulated
std::vector<char> _buffer;
};
} // namespace openspace
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif // __OPENSPACE_CORE___HTTPREQUEST___H__
+168 -42
View File
@@ -26,11 +26,8 @@
#define __OPENSPACE_CORE___RESOURCESYNCHRONIZATION___H__
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
#include <filesystem>
#include <string>
#include <unordered_map>
namespace ghoul { class Dictionary; }
@@ -38,8 +35,152 @@ namespace openspace {
namespace documentation { struct Documentation; }
/**
* The ResourceSynchronization class handles the download of persistent datasets, meaning
* that the contents of the data is only downloaded once at the beginning of the
* application. A ResourceSynchronization is created through the #createFromDictionary
* function, whose dictionary must contain at least a \c Type value that specifies which
* type of ResourceSynchronization should be create. Any type that is registered to the
* global factory is a valid type here.
* A ResourceSynchronization state can be in one of four states that can be queried
* through the #isSyncing, #isResolved, and #isRejected functions. The available states
* are:
* \c Unsynchronized (#isSyncing = false, #isResolved = false, #isRejected = false),
* \c Syncing (#isSyncing = true, #isResolved = false, #isRejected = false),
* \c Resolved (#isSyncing = false, #isResolved = true, #isRejected = false),
* \c Rejected (#isSyncing = false, #isResolved = false, #isRejected = true)
*/
class ResourceSynchronization {
public:
/**
* Creates a new ResourceSynchronization based on the \p dictionary information that
* is passed into this function. The dictionary must contain at least a \c Type, an
* \c Identifier, and a \c Name, with other optional parameters depending on the
* specific subtype of ResourceSynchronization class is is specified through the
* provided \c Type.
*
* \param dictionary The dictionary containing the parameters with which to choose the
* specific type of ResourceSynchronization and all the necessary parameters to
* initialize said ResourceSynchronization
*
* \throw SpecificationError If the \p dictionary does not contain a \c Type, an
* \c Identifier, and a \c Name
*/
static std::unique_ptr<ResourceSynchronization> createFromDictionary(
const ghoul::Dictionary& dictionary);
/**
* Generates a unique identifying string for the dictionary that is based on the
* \c Type and the \c Identifier values of the passed \p dictionary. All other
* parameters are ignored, but as long as the \c Type and/or the \c Identifier values
* differ, the resulting string will be different.
*
* \param dictionary The dictionary containing the \c Type and the \c Identifier used
* to create a unique identifier
*
* \throw SpecificationError If the \p dictionary does not contain a \c Type, an
* \c Identifier, and a \c Name
*/
static std::string generateUid(const ghoul::Dictionary& dictionary);
/// Defaulted virtual constructor
virtual ~ResourceSynchronization() = default;
/**
* Returns the location to which files downloaded through this ResourceSynchronization
* are saved.
*
* \return The location for files created by this class
*/
virtual std::filesystem::path directory() const = 0;
/// Starts the synchronization for this ResourceSynchronization
virtual void start() = 0;
/// Cancels any ongoing synchronization of this ResourceSynchronization
virtual void cancel() = 0;
/**
* Returns the number of bytes that have already been synchronized or 0 if the
* synchronization hasn't started yet. This number always will only contain the number
* of bytes of actual payload data, not any additional data transfers that some
* subtypes might require.
*
* \return The number of synchronized bytes
*/
size_t nSynchronizedBytes() const;
/**
* Returns the number of total bytes that ought to be synchronized for this
* ResourceSynchronization to be considered complete. If that number is not known
* (yet), the returned value is 0. This number always will only contain the number of
* bytes of actual payload data, not any additional data transfers that some subtypes
* might require.
*
* \return The total number of required bytes
*/
size_t nTotalBytes() const;
/**
* Returns \c true if the total number of bytes for this ResourceSynchronization is
* known. Will return \c false otherwise. This number always will only contain the
* number of bytes of actual payload data, not any additional data transfers that some
* subtypes might require.
*
* \return The state whether the number of total bytes is known or not
*/
bool nTotalBytesIsKnown() const;
/**
* Returns the unique identifier of this ResourceSynchronization.
*
* \return The unique identifier of this ResourceSynchronization
*/
const std::string& identifier() const;
/**
* Returns the name of this ResourceSynchronization.
*
* \return The name of this ResourceSynchronization
*/
const std::string& name() const;
/**
* Returns whether this ResourceSynchronization is currently syncing its files and has
* not finished doing so.
*
* \return \c true if this object is currently synchronizing
*/
bool isSyncing() const;
/**
* Returns whether this ResourceSynchronization has successfully finished
* synchronizing all of its files. Once this has returned \c true, it will stay so
* until the object is destroyed and it is guaranteed that no more files will be added
* to the #directory.
*
* \return \c true if this object is finished synchronizing
*/
bool isResolved() const;
/**
* Returns whether this ResourceSynchronization has failed to synchronizing all or any
* of its files. Once this has returned \c true, it will stay so until the object is
* destroyed. Some subclasses might try to download as many files as possible, but no
* general guarantee is provided regarding the completeness of the download.
*
* \return \c true if this object has failed synchronizing one or more of the required
* files
*/
bool isRejected() const;
static documentation::Documentation Documentation();
protected:
/// Empty constructor that just sets the synchronization root
ResourceSynchronization(std::filesystem::path synchronizationRoot);
/// Representation of the state that this object can be in
enum class State {
Unsynced,
Syncing,
@@ -47,49 +188,34 @@ public:
Rejected
};
using CallbackHandle = size_t;
using StateChangeCallback = std::function<void(State)>;
/// Creates a file next to the directory that indicates that this
/// ResourceSynchronization has successfully synchronized its contents
void createSyncFile() const;
static std::unique_ptr<ResourceSynchronization> createFromDictionary(
const ghoul::Dictionary& dictionary);
/// Returns whether the synchronization file create in #createSyncFile exists
bool hasSyncFile() const;
ResourceSynchronization(const ghoul::Dictionary& dictionary);
virtual ~ResourceSynchronization() = default;
virtual std::string directory() = 0;
virtual void start() = 0;
virtual void cancel() = 0;
virtual void clear() = 0;
virtual size_t nSynchronizedBytes() = 0;
virtual size_t nTotalBytes() = 0;
virtual bool nTotalBytesIsKnown() = 0;
virtual float progress();
State state() const;
const std::string& name() const;
bool isResolved() const;
bool isRejected() const;
bool isSyncing() const;
CallbackHandle addStateChangeCallback(StateChangeCallback cb);
void removeStateChangeCallback(CallbackHandle id);
static documentation::Documentation Documentation();
protected:
void resolve();
void reject();
void reset();
void begin();
private:
void setState(State state);
/// The internal identifier for this ResourceSynchronization. It is not enforced but
/// advised that this identifier be different for all instances of the same subtype
std::string _identifier;
/// The user-facing name of this ResourceSynchronization
std::string _name;
/// The path to the root folder relative to which synchronization files are placed
const std::filesystem::path _synchronizationRoot;
/// The current #State of this ResouceSynchronization
std::atomic<State> _state = State::Unsynced;
std::mutex _callbackMutex;
CallbackHandle _nextCallbackId = 0;
std::unordered_map<CallbackHandle, StateChangeCallback> _stateChangeCallbacks;
/// Contains the fact whether the total number of payload bytes is known
std::atomic_bool _nTotalBytesKnown = false;
/// Contains the total number of payload bytes or 0 if that number is not known
std::atomic_size_t _nTotalBytes = 0;
/// Contains the number of already synchronized payload bytes
std::atomic_size_t _nSynchronizedBytes = 0;
};
} // namespace openspace
@@ -1,75 +0,0 @@
/*****************************************************************************************
* *
* OpenSpace *
* *
* Copyright (c) 2014-2021 *
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy of this *
* software and associated documentation files (the "Software"), to deal in the Software *
* without restriction, including without limitation the rights to use, copy, modify, *
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to *
* permit persons to whom the Software is furnished to do so, subject to the following *
* conditions: *
* *
* The above copyright notice and this permission notice shall be included in all copies *
* or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, *
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A *
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT *
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF *
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE *
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *
****************************************************************************************/
#ifndef __OPENSPACE_CORE___SYNCHRONIZATIONWATCHER___H__
#define __OPENSPACE_CORE___SYNCHRONIZATIONWATCHER___H__
#include <openspace/util/resourcesynchronization.h>
#include <memory>
#include <mutex>
#include <vector>
#include <unordered_map>
namespace openspace {
/**
* Delays callbacks of synchronization state changes to
* when notify is called.
*/
class SynchronizationWatcher {
public:
using WatchHandle = size_t;
struct WatchData {
std::weak_ptr<ResourceSynchronization> synchronization;
ResourceSynchronization::CallbackHandle callbackHandle;
};
struct NotificationData {
std::weak_ptr<ResourceSynchronization> synchronization;
ResourceSynchronization::State state;
WatchHandle handle;
ResourceSynchronization::StateChangeCallback callback;
};
WatchHandle watchSynchronization(
std::shared_ptr<ResourceSynchronization> synchronization,
ResourceSynchronization::StateChangeCallback callback
);
void unwatchSynchronization(WatchHandle watchHandle);
void notify();
private:
std::mutex _mutex;
std::unordered_map<WatchHandle, WatchData> _watchedSyncs;
std::vector<NotificationData> _pendingNotifications;
WatchHandle nextWatchHandle = 0;
};
} // namespace openspace
#endif // __OPENSPACE_CORE___SYNCHRONIZATIONWATCHER___H__
+1 -1
View File
@@ -49,7 +49,7 @@ public:
SemanticVersion latestVersion();
private:
std::unique_ptr<AsyncHttpMemoryDownload> _request;
std::unique_ptr<HttpMemoryDownload> _request;
std::optional<SemanticVersion> _latestVersion;
};