[ion] Rework of storage trash

This commit is contained in:
Laury
2022-03-13 21:31:10 +01:00
parent 19e5562228
commit 72a25ec26d
6 changed files with 969 additions and 821 deletions

View File

@@ -31,6 +31,7 @@ ion_src += $(addprefix ion/src/shared/, \
events.cpp \
events_keyboard.cpp \
events_modifier.cpp \
internal_storage.cpp \
platform_info.cpp \
rtc.cpp \
stack_position.cpp \

View File

@@ -0,0 +1,218 @@
#ifndef ION_INTERNAL_STORAGE_H
#define ION_INTERNAL_STORAGE_H
#include <stddef.h>
#include <stdint.h>
#include "storage.h"
namespace Ion {
/* Storage : | Magic | Record1 | Record2 | ... | Magic |
* | Magic | Size1(uint16_t) | FullName1 | Body1 | Size2(uint16_t) | FullName2 | Body2 | ... | Magic |
*
* A record's fullName is baseName.extension. */
class StorageDelegate;
class InternalStorage {
public:
typedef uint16_t record_size_t;
static constexpr char eqExtension[] = "eq";
static constexpr char expExtension[] = "exp";
static constexpr char funcExtension[] = "func";
static constexpr char seqExtension[] = "seq";
constexpr static size_t k_storageSize = 64000;
static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough");
constexpr static char k_dotChar = '.';
class Record {
/* A Record is identified by the CRC32 on its fullName because:
* - A record is identified by its fullName, which is unique
* - We cannot keep the address pointing to the fullName because if another
* record is modified, it might alter our record's fullName address.
* Keeping a buffer with the fullNames will waste memory as we cannot
* forsee the size of the fullNames. */
friend class Storage;
public:
enum class ErrorStatus {
None = 0,
NameTaken = 1,
NonCompliantName = 2,
NotEnoughSpaceAvailable = 3,
RecordDoesNotExist = 4
};
struct Data {
const void * buffer;
size_t size;
};
Record(const char * fullName = nullptr);
Record(const char * basename, const char * extension);
bool operator==(const Record & other) const {
return m_fullNameCRC32 == other.m_fullNameCRC32;
}
bool operator!=(const Record & other) const {
return !(*this == other);
}
#if ION_STORAGE_LOG
void log();
#endif
uint32_t checksum();
bool isNull() const {
return m_fullNameCRC32 == 0;
}
const char * fullName() const;
ErrorStatus setBaseNameWithExtension(const char * baseName, const char * extension);
ErrorStatus setName(const char * fullName);
Data value() const;
ErrorStatus setValue(Data data);
void destroy();
private:
Record(const char * basename, int basenameLength, const char * extension, int extensionLength);
uint32_t m_fullNameCRC32;
};
#if ION_STORAGE_LOG
void log();
#endif
size_t availableSize();
size_t putAvailableSpaceAtEndOfRecord(Record r);
void getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace);
uint32_t checksum();
// Delegate
void setDelegate(StorageDelegate * delegate) { m_delegate = delegate; }
void notifyChangeToDelegate(const Record r = Record()) const;
Record::ErrorStatus notifyFullnessToDelegate() const;
int numberOfRecordsWithExtension(const char * extension);
static bool FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength);
// Record creation
Record::ErrorStatus createRecordWithFullName(const char * fullName, const void * data, size_t size);
Record::ErrorStatus createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size);
// Record getters
bool hasRecord(Record r) { return pointerOfRecord(r) != nullptr; }
Record recordWithExtensionAtIndex(const char * extension, int index);
Record recordNamed(const char * fullName);
Record recordBaseNamedWithExtension(const char * baseName, const char * extension);
Record recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions);
const char * extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions);
// Record destruction
void destroyAllRecords();
void destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension);
void destroyRecordsWithExtension(const char * extension);
// Useful
static bool FullNameCompliant(const char * name);
// Used by Python OS module
int numberOfRecords();
Record recordAtIndex(int index);
protected:
InternalStorage();
/* Getters on address in buffer */
char * pointerOfRecord(const Record record) const;
record_size_t sizeOfRecordStarting(char * start) const;
const char * fullNameOfRecordStarting(char * start) const;
const void * valueOfRecordStarting(char * start) const;
void destroyRecord(const Record record);
class RecordIterator {
public:
RecordIterator(char * start) : m_recordStart(start) {}
char * operator*() { return m_recordStart; }
RecordIterator& operator++();
bool operator!=(const RecordIterator& it) const { return m_recordStart != it.m_recordStart; }
private:
char * m_recordStart;
};
RecordIterator begin() const {
if (sizeOfRecordStarting((char *)m_buffer) == 0) {
return nullptr;
}
return RecordIterator((char *)m_buffer);
};
RecordIterator end() const { return RecordIterator(nullptr); }
mutable Record m_lastRecordRetrieved;
mutable char * m_lastRecordRetrievedPointer;
private:
constexpr static uint32_t Magic = 0xEE0BDDBA;
constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8);
/* Getters/Setters on recordID */
const char * fullNameOfRecord(const Record record);
Record::ErrorStatus setFullNameOfRecord(const Record record, const char * fullName);
Record::ErrorStatus setBaseNameWithExtensionOfRecord(const Record record, const char * baseName, const char * extension);
Record::Data valueOfRecord(const Record record);
Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data);
/* Overriders */
size_t overrideSizeAtPosition(char * position, record_size_t size);
size_t overrideFullNameAtPosition(char * position, const char * fullName);
size_t overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension);
size_t overrideValueAtPosition(char * position, const void * data, record_size_t size);
bool isFullNameTaken(const char * fullName, const Record * recordToExclude = nullptr);
bool isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude = nullptr);
bool isNameOfRecordTaken(Record r, const Record * recordToExclude);
char * endBuffer();
size_t sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const;
size_t sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t size) const;
size_t sizeOfRecordWithFullName(const char * fullName, size_t size) const;
bool slideBuffer(char * position, int delta);
Record privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult = nullptr, int baseNameLength = -1);
uint32_t m_magicHeader;
char m_buffer[k_storageSize];
uint32_t m_magicFooter;
StorageDelegate * m_delegate;
};
/* Some apps memoize records and need to be notified when a record might have
* become invalid. For instance in the Graph app, if f(x) = A+x, and A changed,
* f(x) memoization which stores the reduced expression of f is outdated.
* We could have computed and compared the checksum of the storage to detect
* storage invalidity, but profiling showed that this slows down the execution
* (for example when scrolling the functions list).
* We thus decided to notify a delegate when the storage changes. */
class StorageDelegate {
public:
virtual void storageDidChangeForRecord(const InternalStorage::Record record) = 0;
virtual void storageIsFull() = 0;
};
// emscripten read and writes must be aligned.
class StorageHelper {
public:
static uint16_t unalignedShort(char * address) {
#if __EMSCRIPTEN__
uint8_t f1 = *(address);
uint8_t f2 = *(address+1);
uint16_t f = (uint16_t)f1 + (((uint16_t)f2)<<8);
return f;
#else
return *(uint16_t *)address;
#endif
}
static void writeUnalignedShort(uint16_t value, char * address) {
#if __EMSCRIPTEN__
*((uint8_t *)address) = (uint8_t)(value & ((1 << 8) - 1));
*((uint8_t *)address+1) = (uint8_t)(value >> 8);
#else
*((uint16_t *)address) = value;
#endif
}
};
}
#endif

View File

@@ -1,239 +1,50 @@
#ifndef ION_STORAGE_H
#define ION_STORAGE_H
#include <stddef.h>
#include <stdint.h>
#include "internal_storage.h"
namespace Ion {
/* Storage : | Magic | Record1 | Record2 | ... | Magic |
* | Magic | Size1(uint16_t) | FullName1 | Body1 | Size2(uint16_t) | FullName2 | Body2 | ... | Magic |
*
* A record's fullName is baseName.extension. */
class StorageDelegate;
class Storage {
class Storage : public InternalStorage {
public:
typedef uint16_t record_size_t;
using InternalStorage::Record;
constexpr static size_t k_storageSize = 64000;
static_assert(UINT16_MAX >= k_storageSize, "record_size_t not big enough");
using InternalStorage::expExtension;
using InternalStorage::funcExtension;
using InternalStorage::seqExtension;
using InternalStorage::eqExtension;
static Storage * sharedStorage();
constexpr static char k_dotChar = '.';
static constexpr char eqExtension[] = "eq";
static constexpr char expExtension[] = "exp";
static constexpr char funcExtension[] = "func";
static constexpr char seqExtension[] = "seq";
class Record {
/* A Record is identified by the CRC32 on its fullName because:
* - A record is identified by its fullName, which is unique
* - We cannot keep the address pointing to the fullName because if another
* record is modified, it might alter our record's fullName address.
* Keeping a buffer with the fullNames will waste memory as we cannot
* forsee the size of the fullNames. */
friend class Storage;
public:
enum class ErrorStatus {
None = 0,
NameTaken = 1,
NonCompliantName = 2,
NotEnoughSpaceAvailable = 3,
RecordDoesNotExist = 4
};
struct Data {
const void * buffer;
size_t size;
};
Record(const char * fullName = nullptr);
Record(const char * basename, const char * extension);
bool operator==(const Record & other) const {
return m_fullNameCRC32 == other.m_fullNameCRC32;
}
bool operator!=(const Record & other) const {
return !(*this == other);
}
#if ION_STORAGE_LOG
void log();
#endif
uint32_t checksum();
bool isNull() const {
return m_fullNameCRC32 == 0;
}
const char * fullName() const {
return Storage::sharedStorage()->fullNameOfRecord(*this);
}
ErrorStatus setBaseNameWithExtension(const char * baseName, const char * extension) {
return Storage::sharedStorage()->setBaseNameWithExtensionOfRecord(*this, baseName, extension);
}
ErrorStatus setName(const char * fullName) {
return Storage::sharedStorage()->setFullNameOfRecord(*this, fullName);
}
Data value() const {
return Storage::sharedStorage()->valueOfRecord(*this);
}
ErrorStatus setValue(Data data) {
return Storage::sharedStorage()->setValueOfRecord(*this, data);
}
void destroy() {
return Storage::sharedStorage()->destroyRecord(*this);
}
private:
Record(const char * basename, int basenameLength, const char * extension, int extensionLength);
uint32_t m_fullNameCRC32;
};
#if ION_STORAGE_LOG
void log();
#endif
size_t availableSize();
size_t putAvailableSpaceAtEndOfRecord(Record r);
void getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace);
uint32_t checksum();
// Delegate
void setDelegate(StorageDelegate * delegate) { m_delegate = delegate; }
void notifyChangeToDelegate(const Record r = Record()) const;
Record::ErrorStatus notifyFullnessToDelegate() const;
int numberOfRecordsWithExtension(const char * extension);
static bool FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength);
// Record creation
Record::ErrorStatus createRecordWithFullName(const char * fullName, const void * data, size_t size);
Record::ErrorStatus createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size);
// Record getters
bool hasRecord(Record r) { return pointerOfRecord(r) != nullptr; }
bool hasRecord(Record r);
Record recordWithExtensionAtIndex(const char * extension, int index);
Record recordNamed(const char * fullName);
Record recordBaseNamedWithExtension(const char * baseName, const char * extension);
Record recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions);
const char * extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions);
// Record destruction
void destroyAllRecords();
void destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension);
void destroyRecordsWithExtension(const char * extension);
// Useful
static bool FullNameCompliant(const char * name);
// Used by Python OS module
int numberOfRecords();
Record recordAtIndex(int index); // Unlike realRecordAtIndex, this ignore trash
Record recordAtIndex(int index);
void destroyRecord(Record record);
// Trash
void reinsertTrash(const char * extension);
void emptyTrash();
private:
constexpr static uint32_t Magic = 0xEE0BDDBA;
constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8);
Storage():
InternalStorage() {}
Storage();
/* Getters/Setters on recordID */
const char * fullNameOfRecord(const Record record);
Record::ErrorStatus setFullNameOfRecord(const Record record, const char * fullName);
Record::ErrorStatus setBaseNameWithExtensionOfRecord(const Record record, const char * baseName, const char * extension);
Record::Data valueOfRecord(const Record record);
Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data);
void destroyRecord(const Record record);
void realDestroyRecord(const Record record);
/* Getters on address in buffer */
char * pointerOfRecord(const Record record) const;
record_size_t sizeOfRecordStarting(char * start) const;
const char * fullNameOfRecordStarting(char * start) const;
const void * valueOfRecordStarting(char * start) const;
/* Trash */
void emptyTrash();
Storage::Record realRecordAtIndex(int index);
int realNumberOfRecords();
/* Overriders */
size_t overrideSizeAtPosition(char * position, record_size_t size);
size_t overrideFullNameAtPosition(char * position, const char * fullName);
size_t overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension);
size_t overrideValueAtPosition(char * position, const void * data, record_size_t size);
size_t realAvailableSize();
bool isFullNameTaken(const char * fullName, const Record * recordToExclude = nullptr);
bool isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude = nullptr);
bool isNameOfRecordTaken(Record r, const Record * recordToExclude);
char * endBuffer();
size_t sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const;
size_t sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t size) const;
size_t sizeOfRecordWithFullName(const char * fullName, size_t size) const;
bool slideBuffer(char * position, int delta);
class RecordIterator {
public:
RecordIterator(char * start) : m_recordStart(start) {}
char * operator*() { return m_recordStart; }
RecordIterator& operator++();
bool operator!=(const RecordIterator& it) const { return m_recordStart != it.m_recordStart; }
private:
char * m_recordStart;
};
RecordIterator begin() const {
if (sizeOfRecordStarting((char *)m_buffer) == 0) {
return nullptr;
}
return RecordIterator((char *)m_buffer);
};
RecordIterator end() const { return RecordIterator(nullptr); }
Record privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult = nullptr, int baseNameLength = -1);
uint32_t m_magicHeader;
char m_buffer[k_storageSize];
Record m_trashRecord;
uint32_t m_magicFooter;
StorageDelegate * m_delegate;
mutable Record m_lastRecordRetrieved;
mutable char * m_lastRecordRetrievedPointer;
};
/* Some apps memoize records and need to be notified when a record might have
* become invalid. For instance in the Graph app, if f(x) = A+x, and A changed,
* f(x) memoization which stores the reduced expression of f is outdated.
* We could have computed and compared the checksum of the storage to detect
* storage invalidity, but profiling showed that this slows down the execution
* (for example when scrolling the functions list).
* We thus decided to notify a delegate when the storage changes. */
class StorageDelegate {
public:
virtual void storageDidChangeForRecord(const Storage::Record record) = 0;
virtual void storageIsFull() = 0;
};
// emscripten read and writes must be aligned.
class StorageHelper {
public:
static uint16_t unalignedShort(char * address) {
#if __EMSCRIPTEN__
uint8_t f1 = *(address);
uint8_t f2 = *(address+1);
uint16_t f = (uint16_t)f1 + (((uint16_t)f2)<<8);
return f;
#else
return *(uint16_t *)address;
#endif
}
static void writeUnalignedShort(uint16_t value, char * address) {
#if __EMSCRIPTEN__
*((uint8_t *)address) = (uint8_t)(value & ((1 << 8) - 1));
*((uint8_t *)address+1) = (uint8_t)(value >> 8);
#else
*((uint16_t *)address) = value;
#endif
}
};
}

View File

@@ -0,0 +1,647 @@
#include <ion.h>
#include <string.h>
#include <assert.h>
#include <new>
#include "storage.h"
#if ION_STORAGE_LOG
#include<iostream>
#endif
namespace Ion {
/* We want to implement a simple singleton pattern, to make sure the storage is
* initialized on first use, therefore preventing the static init order fiasco.
* That being said, we rely on knowing where the storage resides in the device's
* memory at compile time. Indeed, we want to advertise the static storage's
* memory address in the PlatformInfo structure (so that we can read and write
* it in DFU).
* Using a "static Storage storage;" variable makes it a local symbol at best,
* preventing the PlatformInfo from retrieving its address. And making the
* Storage variable global yields the static init fiasco issue. We're working
* around both issues by creating a global staticStorageArea buffer, and by
* placement-newing the Storage into that area on first use. */
/* The InternalStorage is handle all records. Then the "normal" Storage handle
* a trash */
constexpr char InternalStorage::expExtension[];
constexpr char InternalStorage::funcExtension[];
constexpr char InternalStorage::seqExtension[];
constexpr char InternalStorage::eqExtension[];
// RECORD
const char * InternalStorage::Record::fullName() const {
return Storage::sharedStorage()->fullNameOfRecord(*this);
}
InternalStorage::Record::ErrorStatus InternalStorage::Record::setBaseNameWithExtension(const char * baseName, const char * extension) {
return Storage::sharedStorage()->setBaseNameWithExtensionOfRecord(*this, baseName, extension);
}
InternalStorage::Record::ErrorStatus InternalStorage::Record::setName(const char * fullName) {
return Storage::sharedStorage()->setFullNameOfRecord(*this, fullName);
}
InternalStorage::Record::Data InternalStorage::Record::value() const {
return Storage::sharedStorage()->valueOfRecord(*this);
}
InternalStorage::Record::ErrorStatus InternalStorage::Record::setValue(Data data) {
return Storage::sharedStorage()->setValueOfRecord(*this, data);
}
void InternalStorage::Record::destroy() {
return Storage::sharedStorage()->destroyRecord(*this);
}
InternalStorage::Record::Record(const char * fullName) {
if (fullName == nullptr) {
m_fullNameCRC32 = 0;
return;
}
const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar);
// If no extension, return empty record
if (*dotChar == 0 || *(dotChar+1) == 0) {
m_fullNameCRC32 = 0;
return;
}
new (this) Record(fullName, dotChar - fullName, dotChar+1, (fullName + strlen(fullName)) - (dotChar+1));
}
InternalStorage::Record::Record(const char * baseName, const char * extension) {
if (baseName == nullptr) {
assert(extension == nullptr);
m_fullNameCRC32 = 0;
return;
}
new (this) Record(baseName, strlen(baseName), extension, strlen(extension));
}
#if ION_STORAGE_LOG
void InternalStorage::Record::log() {
std::cout << "Name: " << fullName() << std::endl;
std::cout << " Value (" << value().size << "): " << (char *)value().buffer << "\n\n" << std::endl;
}
#endif
uint32_t InternalStorage::Record::checksum() {
uint32_t crc32Results[2];
crc32Results[0] = m_fullNameCRC32;
Data data = value();
crc32Results[1] = Ion::crc32Byte((const uint8_t *)data.buffer, data.size);
return Ion::crc32Word(crc32Results, 2);
}
InternalStorage::Record::Record(const char * basename, int basenameLength, const char * extension, int extensionLength) {
assert(basename != nullptr);
assert(extension != nullptr);
// We compute the CRC32 of the CRC32s of the basename and the extension
uint32_t crc32Results[2];
crc32Results[0] = Ion::crc32Byte((const uint8_t *)basename, basenameLength);
crc32Results[1] = Ion::crc32Byte((const uint8_t *)extension, extensionLength);
m_fullNameCRC32 = Ion::crc32Word(crc32Results, 2);
}
// STORAGE
#if ION_STORAGE_LOG
void InternalStorage::log() {
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
Record(currentName).log();
}
}
#endif
size_t InternalStorage::availableSize() {
/* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it
* is needed after calling availableSize */
assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t));
return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t);
}
size_t InternalStorage::putAvailableSpaceAtEndOfRecord(InternalStorage::Record r) {
char * p = pointerOfRecord(r);
size_t previousRecordSize = sizeOfRecordStarting(p);
size_t availableStorageSize = availableSize();
char * nextRecord = p + previousRecordSize;
memmove(nextRecord + availableStorageSize,
nextRecord,
(m_buffer + k_storageSize - availableStorageSize) - nextRecord);
size_t newRecordSize = previousRecordSize + availableStorageSize;
overrideSizeAtPosition(p, (record_size_t)newRecordSize);
return newRecordSize;
}
void InternalStorage::getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace) {
char * p = pointerOfRecord(r);
size_t previousRecordSize = sizeOfRecordStarting(p);
char * nextRecord = p + previousRecordSize;
memmove(nextRecord - recordAvailableSpace,
nextRecord,
m_buffer + k_storageSize - nextRecord);
overrideSizeAtPosition(p, (record_size_t)(previousRecordSize - recordAvailableSpace));
}
uint32_t InternalStorage::checksum() {
return Ion::crc32Byte((const uint8_t *) m_buffer, endBuffer()-m_buffer);
}
void InternalStorage::notifyChangeToDelegate(const Record record) const {
m_lastRecordRetrieved = Record(nullptr);
m_lastRecordRetrievedPointer = nullptr;
if (m_delegate != nullptr) {
m_delegate->storageDidChangeForRecord(record);
}
}
InternalStorage::Record::ErrorStatus InternalStorage::notifyFullnessToDelegate() const {
if (m_delegate != nullptr) {
m_delegate->storageIsFull();
}
return Record::ErrorStatus::NotEnoughSpaceAvailable;
}
InternalStorage::Record::ErrorStatus InternalStorage::createRecordWithFullName(const char * fullName, const void * data, size_t size) {
size_t recordSize = sizeOfRecordWithFullName(fullName, size);
if (recordSize >= k_maxRecordSize || recordSize > availableSize()) {
return notifyFullnessToDelegate();
}
if (isFullNameTaken(fullName)) {
return Record::ErrorStatus::NameTaken;
}
// Find the end of data
char * newRecordAddress = endBuffer();
char * newRecord = newRecordAddress;
// Fill totalSize
newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize);
// Fill name
newRecord += overrideFullNameAtPosition(newRecord, fullName);
// Fill data
newRecord += overrideValueAtPosition(newRecord, data, size);
// Next Record is null-sized
overrideSizeAtPosition(newRecord, 0);
Record r = Record(fullName);
notifyChangeToDelegate(r);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = newRecordAddress;
return Record::ErrorStatus::None;
}
InternalStorage::Record::ErrorStatus InternalStorage::createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size) {
size_t recordSize = sizeOfRecordWithBaseNameAndExtension(baseName, extension, size);
if (recordSize >= k_maxRecordSize || recordSize > availableSize()) {
return notifyFullnessToDelegate();
}
if (isBaseNameWithExtensionTaken(baseName, extension)) {
return Record::ErrorStatus::NameTaken;
}
// Find the end of data
char * newRecordAddress = endBuffer();
char * newRecord = newRecordAddress;
// Fill totalSize
newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize);
// Fill name
newRecord += overrideBaseNameWithExtensionAtPosition(newRecord, baseName, extension);
// Fill data
newRecord += overrideValueAtPosition(newRecord, data, size);
// Next Record is null-sized
overrideSizeAtPosition(newRecord, 0);
Record r = Record(fullNameOfRecordStarting(newRecordAddress));
notifyChangeToDelegate(r);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = newRecordAddress;
return Record::ErrorStatus::None;
}
int InternalStorage::numberOfRecordsWithExtension(const char * extension) {
int count = 0;
size_t extensionLength = strlen(extension);
for (char * p : *this) {
const char * name = fullNameOfRecordStarting(p);
if (FullNameHasExtension(name, extension, extensionLength)) {
count++;
}
}
return count;
}
int InternalStorage::numberOfRecords() {
int count = 0;
for (char * p : *this) {
const char * name = fullNameOfRecordStarting(p);
count++;
}
return count;
}
InternalStorage::Record InternalStorage::recordAtIndex(int index) {
int currentIndex = -1;
const char * name = nullptr;
char * recordAddress = nullptr;
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
currentIndex++;
if (currentIndex == index) {
recordAddress = p;
name = currentName;
break;
}
}
if (name == nullptr) {
return Record();
}
Record r = Record(name);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = recordAddress;
return Record(name);
}
InternalStorage::Record InternalStorage::recordWithExtensionAtIndex(const char * extension, int index) {
int currentIndex = -1;
const char * name = nullptr;
size_t extensionLength = strlen(extension);
char * recordAddress = nullptr;
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
if (FullNameHasExtension(currentName, extension, extensionLength)) {
currentIndex++;
}
if (currentIndex == index) {
recordAddress = p;
name = currentName;
break;
}
}
if (name == nullptr) {
return Record();
}
Record r = Record(name);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = recordAddress;
return Record(name);
}
InternalStorage::Record InternalStorage::recordNamed(const char * fullName) {
if (fullName == nullptr) {
return Record();
}
Record r = Record(fullName);
char * p = pointerOfRecord(r);
if (p != nullptr) {
return r;
}
return Record();
}
InternalStorage::Record InternalStorage::recordBaseNamedWithExtension(const char * baseName, const char * extension) {
const char * extensions[1] = {extension};
return recordBaseNamedWithExtensions(baseName, extensions, 1);
}
InternalStorage::Record InternalStorage::recordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions) {
return privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions);
}
const char * InternalStorage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extensions[], size_t numberOfExtensions) {
const char * result = nullptr;
privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions, &result, baseNameLength);
return result;
}
void InternalStorage::destroyAllRecords() {
overrideSizeAtPosition(m_buffer, 0);
notifyChangeToDelegate();
}
void InternalStorage::destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension) {
recordBaseNamedWithExtension(baseName, extension).destroy();
}
void InternalStorage::destroyRecordsWithExtension(const char * extension) {
size_t extensionLength = strlen(extension);
char * currentRecordStart = (char *)m_buffer;
bool didChange = false;
while (currentRecordStart != nullptr && sizeOfRecordStarting(currentRecordStart) != 0) {
const char * currentFullName = fullNameOfRecordStarting(currentRecordStart);
if (FullNameHasExtension(currentFullName, extension, extensionLength)) {
Record currentRecord(currentFullName);
currentRecord.destroy();
didChange = true;
continue;
}
currentRecordStart = *(RecordIterator(currentRecordStart).operator++());
}
if (didChange) {
notifyChangeToDelegate();
}
}
InternalStorage::InternalStorage() :
m_magicHeader(Magic),
m_buffer(),
m_magicFooter(Magic),
m_delegate(nullptr),
m_lastRecordRetrieved(nullptr),
m_lastRecordRetrievedPointer(nullptr)
{
assert(m_magicHeader == Magic);
assert(m_magicFooter == Magic);
// Set the size of the first record to 0
overrideSizeAtPosition(m_buffer, 0);
}
// PRIVATE
const char * InternalStorage::fullNameOfRecord(const Record record) {
char * p = pointerOfRecord(record);
if (p != nullptr) {
return fullNameOfRecordStarting(p);
}
return nullptr;
}
InternalStorage::Record::ErrorStatus InternalStorage::setFullNameOfRecord(const Record record, const char * fullName) {
if (!FullNameCompliant(fullName)) {
return Record::ErrorStatus::NonCompliantName;
}
if (isFullNameTaken(fullName, &record)) {
return Record::ErrorStatus::NameTaken;
}
size_t nameSize = strlen(fullName) + 1;
char * p = pointerOfRecord(record);
if (p != nullptr) {
size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1;
record_size_t previousRecordSize = sizeOfRecordStarting(p);
size_t newRecordSize = previousRecordSize-previousNameSize+nameSize;
if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) {
return notifyFullnessToDelegate();
}
overrideSizeAtPosition(p, newRecordSize);
overrideFullNameAtPosition(p+sizeof(record_size_t), fullName);
notifyChangeToDelegate(record);
m_lastRecordRetrieved = record;
m_lastRecordRetrievedPointer = p;
return Record::ErrorStatus::None;
}
return Record::ErrorStatus::RecordDoesNotExist;
}
InternalStorage::Record::ErrorStatus InternalStorage::setBaseNameWithExtensionOfRecord(Record record, const char * baseName, const char * extension) {
if (isBaseNameWithExtensionTaken(baseName, extension, &record)) {
return Record::ErrorStatus::NameTaken;
}
size_t nameSize = sizeOfBaseNameAndExtension(baseName, extension);
char * p = pointerOfRecord(record);
if (p != nullptr) {
size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1;
record_size_t previousRecordSize = sizeOfRecordStarting(p);
size_t newRecordSize = previousRecordSize-previousNameSize+nameSize;
if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) {
return notifyFullnessToDelegate();
}
overrideSizeAtPosition(p, newRecordSize);
char * fullNamePosition = p + sizeof(record_size_t);
overrideBaseNameWithExtensionAtPosition(fullNamePosition, baseName, extension);
// Recompute the CRC32
record = Record(fullNamePosition);
notifyChangeToDelegate(record);
m_lastRecordRetrieved = record;
m_lastRecordRetrievedPointer = p;
return Record::ErrorStatus::None;
}
return Record::ErrorStatus::RecordDoesNotExist;
}
InternalStorage::Record::Data InternalStorage::valueOfRecord(const Record record) {
char * p = pointerOfRecord(record);
if (p != nullptr) {
const char * fullName = fullNameOfRecordStarting(p);
record_size_t size = sizeOfRecordStarting(p);
const void * value = valueOfRecordStarting(p);
return {.buffer = value, .size = size-strlen(fullName)-1-sizeof(record_size_t)};
}
return {.buffer= nullptr, .size= 0};
}
InternalStorage::Record::ErrorStatus InternalStorage::setValueOfRecord(Record record, Record::Data data) {
char * p = pointerOfRecord(record);
/* TODO: if data.buffer == p, assert that size hasn't change and do not do any
* memcopy, but still notify the delegate. Beware of scripts and the accordion
* routine.*/
if (p != nullptr) {
record_size_t previousRecordSize = sizeOfRecordStarting(p);
const char * fullName = fullNameOfRecordStarting(p);
size_t newRecordSize = sizeOfRecordWithFullName(fullName, data.size);
if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+previousRecordSize, newRecordSize-previousRecordSize)) {
return notifyFullnessToDelegate();
}
record_size_t fullNameSize = strlen(fullName)+1;
overrideSizeAtPosition(p, newRecordSize);
overrideValueAtPosition(p+sizeof(record_size_t)+fullNameSize, data.buffer, data.size);
notifyChangeToDelegate(record);
m_lastRecordRetrieved = record;
m_lastRecordRetrievedPointer = p;
return Record::ErrorStatus::None;
}
return Record::ErrorStatus::RecordDoesNotExist;
}
void InternalStorage::destroyRecord(Record record) {
if (record.isNull()) {
return;
}
char * p = pointerOfRecord(record);
if (p != nullptr) {
record_size_t previousRecordSize = sizeOfRecordStarting(p);
slideBuffer(p+previousRecordSize, -previousRecordSize);
notifyChangeToDelegate();
}
}
char * InternalStorage::pointerOfRecord(const Record record) const {
if (record.isNull()) {
return nullptr;
}
if (!m_lastRecordRetrieved.isNull() && record == m_lastRecordRetrieved) {
assert(m_lastRecordRetrievedPointer != nullptr);
return m_lastRecordRetrievedPointer;
}
for (char * p : *this) {
Record currentRecord(fullNameOfRecordStarting(p));
if (record == currentRecord) {
m_lastRecordRetrieved = record;
m_lastRecordRetrievedPointer = p;
return p;
}
}
return nullptr;
}
InternalStorage::record_size_t InternalStorage::sizeOfRecordStarting(char * start) const {
return StorageHelper::unalignedShort(start);
}
const char * InternalStorage::fullNameOfRecordStarting(char * start) const {
return start+sizeof(record_size_t);
}
const void * InternalStorage::valueOfRecordStarting(char * start) const {
char * currentChar = start+sizeof(record_size_t);
size_t fullNameLength = strlen(currentChar);
return currentChar+fullNameLength+1;
}
size_t InternalStorage::overrideSizeAtPosition(char * position, record_size_t size) {
StorageHelper::writeUnalignedShort(size, position);
return sizeof(record_size_t);
}
size_t InternalStorage::overrideFullNameAtPosition(char * position, const char * fullName) {
return strlcpy(position, fullName, strlen(fullName)+1) + 1;
}
size_t InternalStorage::overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension) {
size_t result = strlcpy(position, baseName, strlen(baseName)+1); // strlcpy copies the null terminating char
assert(UTF8Decoder::CharSizeOfCodePoint(k_dotChar) == 1);
*(position+result) = k_dotChar; // Replace the null terminating char with a dot
result++;
result += strlcpy(position+result, extension, strlen(extension)+1);
return result+1;
}
size_t InternalStorage::overrideValueAtPosition(char * position, const void * data, record_size_t size) {
memcpy(position, data, size);
return size;
}
bool InternalStorage::isFullNameTaken(const char * fullName, const Record * recordToExclude) {
Record r = Record(fullName);
return isNameOfRecordTaken(r, recordToExclude);
}
bool InternalStorage::isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude) {
Record r = Record(baseName, extension);
return isNameOfRecordTaken(r, recordToExclude);
}
bool InternalStorage::isNameOfRecordTaken(Record r, const Record * recordToExclude) {
if (r == Record()) {
/* If the CRC32 of fullName is 0, we want to refuse the name as it would
* interfere with our escape case in the Record constructor, when the given
* name is nullptr. */
return true;
}
for (char * p : *this) {
Record s(fullNameOfRecordStarting(p));
if (recordToExclude && s == *recordToExclude) {
continue;
}
if (s == r) {
return true;
}
}
return false;
}
bool InternalStorage::FullNameCompliant(const char * fullName) {
// We check that there is one dot and one dot only.
const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar);
if (*dotChar == 0) {
return false;
}
if (*(UTF8Helper::CodePointSearch(dotChar+1, k_dotChar)) == 0) {
return true;
}
return false;
}
bool InternalStorage::FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength) {
if (fullName == nullptr) {
return false;
}
size_t fullNameLength = strlen(fullName);
if (fullNameLength > extensionLength) {
const char * ext = fullName + fullNameLength - extensionLength;
if (UTF8Helper::PreviousCodePointIs(fullName, ext, k_dotChar) && strcmp(ext, extension) == 0) {
return true;
}
}
return false;
}
char * InternalStorage::endBuffer() {
char * currentBuffer = m_buffer;
for (char * p : *this) {
currentBuffer += sizeOfRecordStarting(p);
}
return currentBuffer;
}
size_t InternalStorage::sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const {
// +1 for the dot and +1 for the null terminating char
return strlen(baseName)+1+strlen(extension)+1;
}
size_t InternalStorage::sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t dataSize) const {
return sizeOfBaseNameAndExtension(baseName, extension) + dataSize + sizeof(record_size_t);
}
size_t InternalStorage::sizeOfRecordWithFullName(const char * fullName, size_t dataSize) const {
size_t nameSize = strlen(fullName)+1;
return nameSize+dataSize+sizeof(record_size_t);
}
bool InternalStorage::slideBuffer(char * position, int delta) {
if (delta > (int)availableSize()) {
return false;
}
memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position);
return true;
}
InternalStorage::Record InternalStorage::privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult, int baseNameLength) {
size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength;
{
const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer);
if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0) {
for (size_t i = 0; i < numberOfExtensions; i++) {
if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) {
assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.'));
if (extensionResult != nullptr) {
*extensionResult = extensions[i];
}
return m_lastRecordRetrieved;
}
}
}
}
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
if (strncmp(baseName, currentName, nameLength) == 0) {
for (size_t i = 0; i < numberOfExtensions; i++) {
if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) {
assert(UTF8Helper::CodePointIs(currentName + nameLength, '.'));
if (extensionResult != nullptr) {
*extensionResult = extensions[i];
}
return Record(currentName);
}
}
}
}
if (extensionResult != nullptr) {
*extensionResult = nullptr;
}
return Record();
}
InternalStorage::RecordIterator & InternalStorage::RecordIterator::operator++() {
assert(m_recordStart);
record_size_t size = StorageHelper::unalignedShort(m_recordStart);
char * nextRecord = m_recordStart+size;
record_size_t newRecordSize = StorageHelper::unalignedShort(nextRecord);
m_recordStart = (newRecordSize == 0 ? nullptr : nextRecord);
return *this;
}
}

View File

@@ -1,291 +1,61 @@
#include <ion.h>
#include <string.h>
#include <ion/storage.h>
#include <assert.h>
#include <new>
#if ION_STORAGE_LOG
#include<iostream>
#endif
#include <string.h>
#include <ion/unicode/utf8_helper.h>
namespace Ion {
/* We want to implement a simple singleton pattern, to make sure the storage is
* initialized on first use, therefore preventing the static init order fiasco.
* That being said, we rely on knowing where the storage resides in the device's
* memory at compile time. Indeed, we want to advertise the static storage's
* memory address in the PlatformInfo structure (so that we can read and write
* it in DFU).
* Using a "static Storage storage;" variable makes it a local symbol at best,
* preventing the PlatformInfo from retrieving its address. And making the
* Storage variable global yields the static init fiasco issue. We're working
* around both issues by creating a global staticStorageArea buffer, and by
* placement-newing the Storage into that area on first use. */
uint32_t staticStorageArea[sizeof(Storage)/sizeof(uint32_t)] = {0};
constexpr char Storage::expExtension[];
constexpr char Storage::funcExtension[];
constexpr char Storage::seqExtension[];
constexpr char Storage::eqExtension[];
Storage * Storage::sharedStorage() {
static Storage * storage = new (staticStorageArea) Storage();
return storage;
}
// RECORD
Storage::Record::Record(const char * fullName) {
if (fullName == nullptr) {
m_fullNameCRC32 = 0;
return;
}
const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar);
// If no extension, return empty record
if (*dotChar == 0 || *(dotChar+1) == 0) {
m_fullNameCRC32 = 0;
return;
}
new (this) Record(fullName, dotChar - fullName, dotChar+1, (fullName + strlen(fullName)) - (dotChar+1));
}
Storage::Record::Record(const char * baseName, const char * extension) {
if (baseName == nullptr) {
assert(extension == nullptr);
m_fullNameCRC32 = 0;
return;
}
new (this) Record(baseName, strlen(baseName), extension, strlen(extension));
}
#if ION_STORAGE_LOG
void Storage::Record::log() {
std::cout << "Name: " << fullName() << std::endl;
std::cout << " Value (" << value().size << "): " << (char *)value().buffer << "\n\n" << std::endl;
}
#endif
uint32_t Storage::Record::checksum() {
uint32_t crc32Results[2];
crc32Results[0] = m_fullNameCRC32;
Data data = value();
crc32Results[1] = Ion::crc32Byte((const uint8_t *)data.buffer, data.size);
return Ion::crc32Word(crc32Results, 2);
}
Storage::Record::Record(const char * basename, int basenameLength, const char * extension, int extensionLength) {
assert(basename != nullptr);
assert(extension != nullptr);
// We compute the CRC32 of the CRC32s of the basename and the extension
uint32_t crc32Results[2];
crc32Results[0] = Ion::crc32Byte((const uint8_t *)basename, basenameLength);
crc32Results[1] = Ion::crc32Byte((const uint8_t *)extension, extensionLength);
m_fullNameCRC32 = Ion::crc32Word(crc32Results, 2);
}
// STORAGE
#if ION_STORAGE_LOG
void Storage::log() {
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
Record(currentName).log();
}
}
#endif
size_t Storage::availableSize() {
if (m_trashRecord != NULL) {
return realAvailableSize() + sizeof(record_size_t) + m_trashRecord.value().size;
return InternalStorage::availableSize() + sizeof(record_size_t) + m_trashRecord.value().size;
} else {
return realAvailableSize();
return InternalStorage::availableSize();
}
}
size_t Storage::realAvailableSize() {
/* TODO maybe do: availableSize(char ** endBuffer) to get the endBuffer if it
* is needed after calling availableSize */
assert(k_storageSize >= (endBuffer() - m_buffer) + sizeof(record_size_t));
return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t);
}
size_t Storage::putAvailableSpaceAtEndOfRecord(Storage::Record r) {
char * p = pointerOfRecord(r);
size_t previousRecordSize = sizeOfRecordStarting(p);
size_t availableStorageSize = realAvailableSize();
char * nextRecord = p + previousRecordSize;
memmove(nextRecord + availableStorageSize,
nextRecord,
(m_buffer + k_storageSize - availableStorageSize) - nextRecord);
size_t newRecordSize = previousRecordSize + availableStorageSize;
overrideSizeAtPosition(p, (record_size_t)newRecordSize);
return newRecordSize;
size_t Storage::putAvailableSpaceAtEndOfRecord(Record r) {
emptyTrash();
return InternalStorage::putAvailableSpaceAtEndOfRecord(r);
}
void Storage::getAvailableSpaceFromEndOfRecord(Record r, size_t recordAvailableSpace) {
char * p = pointerOfRecord(r);
size_t previousRecordSize = sizeOfRecordStarting(p);
char * nextRecord = p + previousRecordSize;
memmove(nextRecord - recordAvailableSpace,
nextRecord,
m_buffer + k_storageSize - nextRecord);
overrideSizeAtPosition(p, (record_size_t)(previousRecordSize - recordAvailableSpace));
emptyTrash();
InternalStorage::getAvailableSpaceFromEndOfRecord(r, recordAvailableSpace);
}
uint32_t Storage::checksum() {
return Ion::crc32Byte((const uint8_t *) m_buffer, endBuffer()-m_buffer);
}
void Storage::notifyChangeToDelegate(const Record record) const {
m_lastRecordRetrieved = Record(nullptr);
m_lastRecordRetrievedPointer = nullptr;
if (m_delegate != nullptr) {
m_delegate->storageDidChangeForRecord(record);
int Storage::numberOfRecordsWithExtension(const char * extension) {
int trashRecord = 0;
if (FullNameHasExtension(m_trashRecord.fullName(), extension, strlen(extension))) {
trashRecord = 1;
}
}
Storage::Record::ErrorStatus Storage::notifyFullnessToDelegate() const {
if (m_delegate != nullptr) {
m_delegate->storageIsFull();
}
return Record::ErrorStatus::NotEnoughSpaceAvailable;
return InternalStorage::numberOfRecordsWithExtension(extension) - trashRecord;
}
Storage::Record::ErrorStatus Storage::createRecordWithFullName(const char * fullName, const void * data, size_t size) {
emptyTrash();
size_t recordSize = sizeOfRecordWithFullName(fullName, size);
if (recordSize >= k_maxRecordSize || recordSize > availableSize()) {
return notifyFullnessToDelegate();
}
if (isFullNameTaken(fullName)) {
return Record::ErrorStatus::NameTaken;
}
// Find the end of data
char * newRecordAddress = endBuffer();
char * newRecord = newRecordAddress;
// Fill totalSize
newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize);
// Fill name
newRecord += overrideFullNameAtPosition(newRecord, fullName);
// Fill data
newRecord += overrideValueAtPosition(newRecord, data, size);
// Next Record is null-sized
overrideSizeAtPosition(newRecord, 0);
Record r = Record(fullName);
notifyChangeToDelegate(r);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = newRecordAddress;
return Record::ErrorStatus::None;
return InternalStorage::createRecordWithFullName(fullName, data, size);
}
Storage::Record::ErrorStatus Storage::createRecordWithExtension(const char * baseName, const char * extension, const void * data, size_t size) {
emptyTrash();
size_t recordSize = sizeOfRecordWithBaseNameAndExtension(baseName, extension, size);
if (recordSize >= k_maxRecordSize || recordSize > availableSize()) {
return notifyFullnessToDelegate();
}
if (isBaseNameWithExtensionTaken(baseName, extension)) {
return Record::ErrorStatus::NameTaken;
}
// Find the end of data
char * newRecordAddress = endBuffer();
char * newRecord = newRecordAddress;
// Fill totalSize
newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize);
// Fill name
newRecord += overrideBaseNameWithExtensionAtPosition(newRecord, baseName, extension);
// Fill data
newRecord += overrideValueAtPosition(newRecord, data, size);
// Next Record is null-sized
overrideSizeAtPosition(newRecord, 0);
Record r = Record(fullNameOfRecordStarting(newRecordAddress));
notifyChangeToDelegate(r);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = newRecordAddress;
return Record::ErrorStatus::None;
return InternalStorage::createRecordWithExtension(baseName, extension, data, size);
}
int Storage::numberOfRecordsWithExtension(const char * extension) {
int count = 0;
size_t extensionLength = strlen(extension);
for (char * p : *this) {
const char * name = fullNameOfRecordStarting(p);
if (FullNameHasExtension(name, extension, extensionLength) && Record(name) != m_trashRecord) {
count++;
}
}
return count;
bool Storage::hasRecord(Record r) {
return InternalStorage::hasRecord(r) && r != m_trashRecord;
}
int Storage::numberOfRecords() {
return realNumberOfRecords() - (m_trashRecord == NULL ? 0 : 1);
}
int Storage::realNumberOfRecords() {
int count = 0;
for (char * p : *this) {
const char * name = fullNameOfRecordStarting(p);
count++;
}
return count;
}
Storage::Record Storage::realRecordAtIndex(int index) {
int currentIndex = -1;
const char * name = nullptr;
char * recordAddress = nullptr;
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
currentIndex++;
if (currentIndex == index) {
recordAddress = p;
name = currentName;
break;
}
}
if (name == nullptr) {
return Record();
}
Record r = Record(name);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = recordAddress;
return Record(name);
}
Storage::Record Storage::recordAtIndex(int index) {
int currentIndex = -1;
const char * name = nullptr;
char * recordAddress = nullptr;
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
Record r = Record(currentName);
if (r == m_trashRecord) {
continue;
}
currentIndex++;
if (currentIndex == index) {
recordAddress = p;
name = currentName;
break;
}
}
if (name == nullptr) {
return Record();
}
Record r = Record(name);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = recordAddress;
return Record(name);
}
void Storage::emptyTrash() {
if (m_trashRecord != NULL) {
realDestroyRecord(m_trashRecord);
m_trashRecord = NULL;
}
void Storage::destroyRecord(Record record) {
emptyTrash();
m_trashRecord = record;
}
Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int index) {
@@ -307,397 +77,98 @@ Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int
if (name == nullptr) {
return Record();
}
Record r = Record(name);
Storage::Record r = Record(name);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = recordAddress;
return Record(name);
}
Storage::Record Storage::recordNamed(const char * fullName) {
if (fullName == nullptr) {
return Record();
}
Record r = Record(fullName);
char * p = pointerOfRecord(r);
if (p != nullptr) {
return r;
}
return Record();
Storage::Record r = InternalStorage::recordNamed(fullName);
return r == m_trashRecord ? Record() : r;
}
Storage::Record Storage::recordBaseNamedWithExtension(const char * baseName, const char * extension) {
const char * extensions[1] = {extension};
return recordBaseNamedWithExtensions(baseName, extensions, 1);
Storage::Record r = InternalStorage::recordBaseNamedWithExtension(baseName, extension);
return r == m_trashRecord ? Record() : r;
}
Storage::Record Storage::recordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions) {
return privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions);
Storage::Record Storage::recordBaseNamedWithExtensions(const char * baseName, const char * const extension[], size_t numberOfExtensions) {
Storage::Record r = InternalStorage::recordBaseNamedWithExtensions(baseName, extension, numberOfExtensions);
return r == m_trashRecord ? Record() : r;
}
const char * Storage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extensions[], size_t numberOfExtensions) {
const char * result = nullptr;
privateRecordAndExtensionOfRecordBaseNamedWithExtensions(baseName, extensions, numberOfExtensions, &result, baseNameLength);
return result;
}
void Storage::destroyAllRecords() {
overrideSizeAtPosition(m_buffer, 0);
notifyChangeToDelegate();
}
void Storage::destroyRecordWithBaseNameAndExtension(const char * baseName, const char * extension) {
recordBaseNamedWithExtension(baseName, extension).destroy();
}
void Storage::destroyRecordsWithExtension(const char * extension) {
size_t extensionLength = strlen(extension);
char * currentRecordStart = (char *)m_buffer;
bool didChange = false;
while (currentRecordStart != nullptr && sizeOfRecordStarting(currentRecordStart) != 0) {
const char * currentFullName = fullNameOfRecordStarting(currentRecordStart);
if (FullNameHasExtension(currentFullName, extension, extensionLength)) {
Record currentRecord(currentFullName);
currentRecord.destroy();
didChange = true;
continue;
}
currentRecordStart = *(RecordIterator(currentRecordStart).operator++());
}
if (didChange) {
notifyChangeToDelegate();
}
}
// PRIVATE
Storage::Storage() :
m_magicHeader(Magic),
m_buffer(),
m_magicFooter(Magic),
m_delegate(nullptr),
m_lastRecordRetrieved(nullptr),
m_lastRecordRetrievedPointer(nullptr),
m_trashRecord(NULL)
{
assert(m_magicHeader == Magic);
assert(m_magicFooter == Magic);
// Set the size of the first record to 0
overrideSizeAtPosition(m_buffer, 0);
}
const char * Storage::fullNameOfRecord(const Record record) {
char * p = pointerOfRecord(record);
if (p != nullptr) {
return fullNameOfRecordStarting(p);
}
return nullptr;
}
Storage::Record::ErrorStatus Storage::setFullNameOfRecord(const Record record, const char * fullName) {
if (!FullNameCompliant(fullName)) {
return Record::ErrorStatus::NonCompliantName;
}
if (isFullNameTaken(fullName, &record)) {
return Record::ErrorStatus::NameTaken;
}
size_t nameSize = strlen(fullName) + 1;
char * p = pointerOfRecord(record);
if (p != nullptr) {
size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1;
record_size_t previousRecordSize = sizeOfRecordStarting(p);
size_t newRecordSize = previousRecordSize-previousNameSize+nameSize;
if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) {
return notifyFullnessToDelegate();
}
overrideSizeAtPosition(p, newRecordSize);
overrideFullNameAtPosition(p+sizeof(record_size_t), fullName);
notifyChangeToDelegate(record);
m_lastRecordRetrieved = record;
m_lastRecordRetrievedPointer = p;
return Record::ErrorStatus::None;
}
return Record::ErrorStatus::RecordDoesNotExist;
}
Storage::Record::ErrorStatus Storage::setBaseNameWithExtensionOfRecord(Record record, const char * baseName, const char * extension) {
if (isBaseNameWithExtensionTaken(baseName, extension, &record)) {
return Record::ErrorStatus::NameTaken;
}
size_t nameSize = sizeOfBaseNameAndExtension(baseName, extension);
char * p = pointerOfRecord(record);
if (p != nullptr) {
size_t previousNameSize = strlen(fullNameOfRecordStarting(p))+1;
record_size_t previousRecordSize = sizeOfRecordStarting(p);
size_t newRecordSize = previousRecordSize-previousNameSize+nameSize;
if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+sizeof(record_size_t)+previousNameSize, nameSize-previousNameSize)) {
return notifyFullnessToDelegate();
}
overrideSizeAtPosition(p, newRecordSize);
char * fullNamePosition = p + sizeof(record_size_t);
overrideBaseNameWithExtensionAtPosition(fullNamePosition, baseName, extension);
// Recompute the CRC32
record = Record(fullNamePosition);
notifyChangeToDelegate(record);
m_lastRecordRetrieved = record;
m_lastRecordRetrievedPointer = p;
return Record::ErrorStatus::None;
}
return Record::ErrorStatus::RecordDoesNotExist;
}
Storage::Record::Data Storage::valueOfRecord(const Record record) {
char * p = pointerOfRecord(record);
if (p != nullptr) {
const char * fullName = fullNameOfRecordStarting(p);
record_size_t size = sizeOfRecordStarting(p);
const void * value = valueOfRecordStarting(p);
return {.buffer = value, .size = size-strlen(fullName)-1-sizeof(record_size_t)};
}
return {.buffer= nullptr, .size= 0};
}
Storage::Record::ErrorStatus Storage::setValueOfRecord(Record record, Record::Data data) {
char * p = pointerOfRecord(record);
/* TODO: if data.buffer == p, assert that size hasn't change and do not do any
* memcopy, but still notify the delegate. Beware of scripts and the accordion
* routine.*/
if (p != nullptr) {
record_size_t previousRecordSize = sizeOfRecordStarting(p);
const char * fullName = fullNameOfRecordStarting(p);
size_t newRecordSize = sizeOfRecordWithFullName(fullName, data.size);
if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+previousRecordSize, newRecordSize-previousRecordSize)) {
return notifyFullnessToDelegate();
}
record_size_t fullNameSize = strlen(fullName)+1;
overrideSizeAtPosition(p, newRecordSize);
overrideValueAtPosition(p+sizeof(record_size_t)+fullNameSize, data.buffer, data.size);
notifyChangeToDelegate(record);
m_lastRecordRetrieved = record;
m_lastRecordRetrievedPointer = p;
return Record::ErrorStatus::None;
}
return Record::ErrorStatus::RecordDoesNotExist;
}
void Storage::reinsertTrash(const char * extension) {
if (m_trashRecord != NULL) {
char * p = pointerOfRecord(m_trashRecord);
const char * fullName = fullNameOfRecordStarting(p);
if (FullNameHasExtension(fullName, extension, strlen(extension))) {
m_trashRecord = NULL;
}
}
}
void Storage::destroyRecord(Record record) {
emptyTrash();
m_trashRecord = record;
}
void Storage::realDestroyRecord(Record record) {
if (record.isNull()) {
return;
}
char * p = pointerOfRecord(record);
if (p != nullptr) {
record_size_t previousRecordSize = sizeOfRecordStarting(p);
slideBuffer(p+previousRecordSize, -previousRecordSize);
notifyChangeToDelegate();
}
}
char * Storage::pointerOfRecord(const Record record) const {
if (record.isNull()) {
return nullptr;
}
if (!m_lastRecordRetrieved.isNull() && record == m_lastRecordRetrieved) {
assert(m_lastRecordRetrievedPointer != nullptr);
return m_lastRecordRetrievedPointer;
}
for (char * p : *this) {
Record currentRecord(fullNameOfRecordStarting(p));
if (record == currentRecord) {
m_lastRecordRetrieved = record;
m_lastRecordRetrievedPointer = p;
return p;
}
}
return nullptr;
}
Storage::record_size_t Storage::sizeOfRecordStarting(char * start) const {
return StorageHelper::unalignedShort(start);
}
const char * Storage::fullNameOfRecordStarting(char * start) const {
return start+sizeof(record_size_t);
}
const void * Storage::valueOfRecordStarting(char * start) const {
char * currentChar = start+sizeof(record_size_t);
size_t fullNameLength = strlen(currentChar);
return currentChar+fullNameLength+1;
}
size_t Storage::overrideSizeAtPosition(char * position, record_size_t size) {
StorageHelper::writeUnalignedShort(size, position);
return sizeof(record_size_t);
}
size_t Storage::overrideFullNameAtPosition(char * position, const char * fullName) {
return strlcpy(position, fullName, strlen(fullName)+1) + 1;
}
size_t Storage::overrideBaseNameWithExtensionAtPosition(char * position, const char * baseName, const char * extension) {
size_t result = strlcpy(position, baseName, strlen(baseName)+1); // strlcpy copies the null terminating char
assert(UTF8Decoder::CharSizeOfCodePoint(k_dotChar) == 1);
*(position+result) = k_dotChar; // Replace the null terminating char with a dot
result++;
result += strlcpy(position+result, extension, strlen(extension)+1);
return result+1;
}
size_t Storage::overrideValueAtPosition(char * position, const void * data, record_size_t size) {
memcpy(position, data, size);
return size;
}
bool Storage::isFullNameTaken(const char * fullName, const Record * recordToExclude) {
Record r = Record(fullName);
return isNameOfRecordTaken(r, recordToExclude);
}
bool Storage::isBaseNameWithExtensionTaken(const char * baseName, const char * extension, Record * recordToExclude) {
Record r = Record(baseName, extension);
return isNameOfRecordTaken(r, recordToExclude);
}
bool Storage::isNameOfRecordTaken(Record r, const Record * recordToExclude) {
if (r == Record()) {
/* If the CRC32 of fullName is 0, we want to refuse the name as it would
* interfere with our escape case in the Record constructor, when the given
* name is nullptr. */
return true;
}
for (char * p : *this) {
Record s(fullNameOfRecordStarting(p));
if (recordToExclude && s == *recordToExclude) {
continue;
}
if (s == r && s != m_trashRecord) {
return true;
}
}
return false;
}
bool Storage::FullNameCompliant(const char * fullName) {
// We check that there is one dot and one dot only.
const char * dotChar = UTF8Helper::CodePointSearch(fullName, k_dotChar);
if (*dotChar == 0) {
return false;
}
if (*(UTF8Helper::CodePointSearch(dotChar+1, k_dotChar)) == 0) {
return true;
}
return false;
}
bool Storage::FullNameHasExtension(const char * fullName, const char * extension, size_t extensionLength) {
if (fullName == nullptr) {
return false;
}
size_t fullNameLength = strlen(fullName);
if (fullNameLength > extensionLength) {
const char * ext = fullName + fullNameLength - extensionLength;
if (UTF8Helper::PreviousCodePointIs(fullName, ext, k_dotChar) && strcmp(ext, extension) == 0) {
return true;
}
}
return false;
}
char * Storage::endBuffer() {
char * currentBuffer = m_buffer;
for (char * p : *this) {
currentBuffer += sizeOfRecordStarting(p);
}
return currentBuffer;
}
size_t Storage::sizeOfBaseNameAndExtension(const char * baseName, const char * extension) const {
// +1 for the dot and +1 for the null terminating char
return strlen(baseName)+1+strlen(extension)+1;
}
size_t Storage::sizeOfRecordWithBaseNameAndExtension(const char * baseName, const char * extension, size_t dataSize) const {
return sizeOfBaseNameAndExtension(baseName, extension) + dataSize + sizeof(record_size_t);
}
size_t Storage::sizeOfRecordWithFullName(const char * fullName, size_t dataSize) const {
size_t nameSize = strlen(fullName)+1;
return nameSize+dataSize+sizeof(record_size_t);
}
bool Storage::slideBuffer(char * position, int delta) {
if (delta > (int)realAvailableSize()) {
emptyTrash();
if (delta > (int)realAvailableSize()) {
return false;
}
}
memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position);
return true;
}
Storage::Record Storage::privateRecordAndExtensionOfRecordBaseNamedWithExtensions(const char * baseName, const char * const extensions[], size_t numberOfExtensions, const char * * extensionResult, int baseNameLength) {
const char * Storage::extensionOfRecordBaseNamedWithExtensions(const char * baseName, int baseNameLength, const char * const extension[], size_t numberOfExtensions) {
size_t nameLength = baseNameLength < 0 ? strlen(baseName) : baseNameLength;
{
const char * lastRetrievedRecordFullName = fullNameOfRecordStarting(m_lastRecordRetrievedPointer);
if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0 && Record(lastRetrievedRecordFullName) == m_trashRecord) {
if (m_lastRecordRetrievedPointer != nullptr && strncmp(baseName, lastRetrievedRecordFullName, nameLength) == 0 && Record(lastRetrievedRecordFullName) != m_trashRecord) {
for (size_t i = 0; i < numberOfExtensions; i++) {
if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) {
if (strcmp(lastRetrievedRecordFullName+nameLength+1 /*+1 to pass the dot*/, extension[i]) == 0) {
assert(UTF8Helper::CodePointIs(lastRetrievedRecordFullName + nameLength, '.'));
if (extensionResult != nullptr) {
*extensionResult = extensions[i];
}
return m_lastRecordRetrieved;
return extension[i];
}
}
}
}
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
if (strncmp(baseName, currentName, nameLength) == 0) {
if (Record(currentName) == m_trashRecord) {
continue;
}
if (strncmp(baseName, currentName, nameLength) == 0 && Record(currentName) != m_trashRecord) {
for (size_t i = 0; i < numberOfExtensions; i++) {
if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extensions[i]) == 0) {
if (strcmp(currentName+nameLength+1 /*+1 to pass the dot*/, extension[i]) == 0) {
assert(UTF8Helper::CodePointIs(currentName + nameLength, '.'));
if (extensionResult != nullptr) {
*extensionResult = extensions[i];
}
return Record(currentName);
return extension[i];
}
}
}
}
if (extensionResult != nullptr) {
*extensionResult = nullptr;
return nullptr;
}
int Storage::numberOfRecords() {
return InternalStorage::numberOfRecords() - (m_trashRecord != NULL ? 1 : 0);
}
InternalStorage::Record Storage::recordAtIndex(int index) {
int currentIndex = -1;
const char * name = nullptr;
char * recordAddress = nullptr;
for (char * p : *this) {
const char * currentName = fullNameOfRecordStarting(p);
if (Record(currentName) != m_trashRecord) {
currentIndex++;
if (currentIndex == index) {
recordAddress = p;
name = currentName;
break;
}
}
}
return Record();
if (name == nullptr) {
return Record();
}
Record r = Record(name);
m_lastRecordRetrieved = r;
m_lastRecordRetrievedPointer = recordAddress;
return Record(name);
}
Storage::RecordIterator & Storage::RecordIterator::operator++() {
assert(m_recordStart);
record_size_t size = StorageHelper::unalignedShort(m_recordStart);
char * nextRecord = m_recordStart+size;
record_size_t newRecordSize = StorageHelper::unalignedShort(nextRecord);
m_recordStart = (newRecordSize == 0 ? nullptr : nextRecord);
return *this;
void Storage::reinsertTrash(const char * extension) {
if (!m_trashRecord.isNull()) {
char * p = pointerOfRecord(m_trashRecord);
const char * fullName = fullNameOfRecordStarting(p);
if (FullNameHasExtension(fullName, extension, strlen(extension))) {
m_trashRecord = Record();
}
}
}
void Storage::emptyTrash() {
if (!m_trashRecord.isNull()) {
InternalStorage::destroyRecord(m_trashRecord);
m_trashRecord = Record();
}
}
}