mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
[ion] Rework of storage trash
This commit is contained in:
@@ -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 \
|
||||
|
||||
218
ion/include/ion/internal_storage.h
Normal file
218
ion/include/ion/internal_storage.h
Normal 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
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
647
ion/src/shared/internal_storage.cpp
Normal file
647
ion/src/shared/internal_storage.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user