From d1b91cb436d1e602452aa358477108bef61ab86a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 17 Feb 2017 09:18:51 -0800 Subject: [PATCH] Adding abstracted storage for in-memory or file backed data --- libraries/shared/src/shared/Storage.cpp | 86 +++++++++++++++++++++++++ libraries/shared/src/shared/Storage.h | 65 +++++++++++++++++++ tests/shared/src/StorageTests.cpp | 75 +++++++++++++++++++++ tests/shared/src/StorageTests.h | 32 +++++++++ 4 files changed, 258 insertions(+) create mode 100644 libraries/shared/src/shared/Storage.cpp create mode 100644 libraries/shared/src/shared/Storage.h create mode 100644 tests/shared/src/StorageTests.cpp create mode 100644 tests/shared/src/StorageTests.h diff --git a/libraries/shared/src/shared/Storage.cpp b/libraries/shared/src/shared/Storage.cpp new file mode 100644 index 0000000000..fe17d678c7 --- /dev/null +++ b/libraries/shared/src/shared/Storage.cpp @@ -0,0 +1,86 @@ +// +// Created by Bradley Austin Davis on 2016/02/17 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Storage.h" + +#include + +using namespace storage; + +MemoryStoragePointer Storage::toMemoryStorage() const { + return std::make_unique(size(), data()); +} + +FileStoragePointer Storage::toFileStorage(const QString& filename) const { + return FileStorage::create(filename, size(), data()); +} + +MemoryStorage::MemoryStorage(size_t size, const uint8_t* data) { + _data.resize(size); + memcpy(_data.data(), data, size); +} + +const uint8_t* MemoryStorage::data() const { + return _data.data(); +} + +size_t MemoryStorage::size() const { + return _data.size(); +} + +FileStoragePointer FileStorage::create(const QString& filename, size_t size, const uint8_t* data) { + QFile file(filename); + if (!file.open(QFile::ReadWrite | QIODevice::Truncate)) { + throw std::runtime_error("Unable to open file for writing"); + } + if (!file.resize(size)) { + throw std::runtime_error("Unable to resize file"); + } + { + auto mapped = file.map(0, size); + if (!mapped) { + throw std::runtime_error("Unable to map file"); + } + memcpy(mapped, data, size); + if (!file.unmap(mapped)) { + throw std::runtime_error("Unable to unmap file"); + } + } + file.close(); + return std::make_unique(filename); +} + +FileStorage::FileStorage(const QString& filename) : _file(filename) { + if (!_file.open(QFile::ReadOnly)) { + throw std::runtime_error("Unable to open file"); + } + _mapped = _file.map(0, _file.size()); + if (!_mapped) { + throw std::runtime_error("Unable to map file"); + } +} + +FileStorage::~FileStorage() { + if (_mapped) { + if (!_file.unmap(_mapped)) { + throw std::runtime_error("Unable to unmap file"); + } + } + if (_file.isOpen()) { + _file.close(); + } +} + + +const uint8_t* FileStorage::data() const { + return _mapped; +} + +size_t FileStorage::size() const { + return _file.size(); +} diff --git a/libraries/shared/src/shared/Storage.h b/libraries/shared/src/shared/Storage.h new file mode 100644 index 0000000000..3198172b06 --- /dev/null +++ b/libraries/shared/src/shared/Storage.h @@ -0,0 +1,65 @@ +// +// Created by Bradley Austin Davis on 2016/02/17 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#pragma once +#ifndef hifi_Storage_h +#define hifi_Storage_h + +#include +#include +#include +#include +#include + +namespace storage { + class Storage; + using StoragePointer = std::unique_ptr; + class MemoryStorage; + using MemoryStoragePointer = std::unique_ptr; + class FileStorage; + using FileStoragePointer = std::unique_ptr; + + class Storage { + public: + virtual ~Storage() {} + virtual const uint8_t* data() const = 0; + virtual size_t size() const = 0; + + FileStoragePointer toFileStorage(const QString& filename) const; + MemoryStoragePointer toMemoryStorage() const; + }; + + class MemoryStorage : public Storage { + public: + MemoryStorage(size_t size, const uint8_t* data); + const uint8_t* data() const override; + size_t size() const override; + private: + std::vector _data; + }; + + class FileStorage : public Storage { + public: + static FileStoragePointer create(const QString& filename, size_t size, const uint8_t* data); + FileStorage(const QString& filename); + ~FileStorage(); + // Prevent copying + FileStorage(const FileStorage& other) = delete; + FileStorage& operator=(const FileStorage& other) = delete; + + const uint8_t* data() const override; + size_t size() const override; + private: + QFile _file; + uint8_t* _mapped { nullptr }; + }; + + +} + +#endif // hifi_Storage_h diff --git a/tests/shared/src/StorageTests.cpp b/tests/shared/src/StorageTests.cpp new file mode 100644 index 0000000000..fa538f6911 --- /dev/null +++ b/tests/shared/src/StorageTests.cpp @@ -0,0 +1,75 @@ +// +// Created by Bradley Austin Davis on 2016/02/17 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "StorageTests.h" + +QTEST_MAIN(StorageTests) + +using namespace storage; + +StorageTests::StorageTests() { + for (size_t i = 0; i < _testData.size(); ++i) { + _testData[i] = (uint8_t)rand(); + } + _testFile = QDir::tempPath() + "/" + QUuid::createUuid().toString(); +} + +StorageTests::~StorageTests() { + QFileInfo fileInfo(_testFile); + if (fileInfo.exists()) { + QFile(_testFile).remove(); + } +} + + +void StorageTests::testConversion() { + { + QFileInfo fileInfo(_testFile); + QCOMPARE(fileInfo.exists(), false); + } + StoragePointer storagePointer = std::make_unique(_testData.size(), _testData.data()); + QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); + // Convert to a file + storagePointer = storagePointer->toFileStorage(_testFile); + { + QFileInfo fileInfo(_testFile); + QCOMPARE(fileInfo.exists(), true); + QCOMPARE(fileInfo.size(), (qint64)_testData.size()); + } + QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); + + // Convert to memory + storagePointer = storagePointer->toMemoryStorage(); + QCOMPARE(storagePointer->size(), (quint64)_testData.size()); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), _testData.size()), 0); + { + // ensure the file is unaffected + QFileInfo fileInfo(_testFile); + QCOMPARE(fileInfo.exists(), true); + QCOMPARE(fileInfo.size(), (qint64)_testData.size()); + } + + // truncate the data as a new memory object + auto newSize = _testData.size() / 2; + storagePointer = std::make_unique(newSize, storagePointer->data()); + QCOMPARE(storagePointer->size(), (quint64)newSize); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); + + // Convert back to file + storagePointer = storagePointer->toFileStorage(_testFile); + QCOMPARE(storagePointer->size(), (quint64)newSize); + QCOMPARE(memcmp(_testData.data(), storagePointer->data(), newSize), 0); + { + // ensure the file is truncated + QFileInfo fileInfo(_testFile); + QCOMPARE(fileInfo.exists(), true); + QCOMPARE(fileInfo.size(), (qint64)newSize); + } +} diff --git a/tests/shared/src/StorageTests.h b/tests/shared/src/StorageTests.h new file mode 100644 index 0000000000..6a2c153223 --- /dev/null +++ b/tests/shared/src/StorageTests.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis on 2016/02/17 +// Copyright 2013-2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_StorageTests_h +#define hifi_StorageTests_h + +#include + +#include +#include + +class StorageTests : public QObject { + Q_OBJECT + +public: + StorageTests(); + ~StorageTests(); + +private slots: + void testConversion(); + +private: + std::array _testData; + QString _testFile; +}; + +#endif // hifi_StorageTests_h