[ion] Change Storage design (former kallax)

This commit is contained in:
Émilie Feral
2018-03-02 15:19:35 +01:00
committed by EmilieNumworks
parent 1f351ddbd6
commit b0ede47d55
19 changed files with 475 additions and 421 deletions

View File

@@ -5,13 +5,15 @@
#include <apps/code/app.h>
#include <escher/metric.h>
extern Ion::Storage storage;
namespace Code {
EditorController::EditorController(MenuController * menuController) :
ViewController(nullptr),
m_textArea(this),
m_areaBuffer(nullptr),
m_script(Ion::Record()),
m_script(Ion::Storage::Record()),
m_menuController(menuController)
{
m_textArea.setDelegate(this);
@@ -26,7 +28,7 @@ void EditorController::setScript(Script script) {
m_script = script;
const char * scriptBody = m_script.readContent();
size_t scriptBodySize = strlen(scriptBody)+1;
size_t availableScriptSize = scriptBodySize + Ion::Kallax::sharedKallax()->availableSize();
size_t availableScriptSize = scriptBodySize + storage.availableSize();
assert(m_areaBuffer == nullptr);
m_areaBuffer = new char[availableScriptSize];
strlcpy(m_areaBuffer, scriptBody, scriptBodySize);
@@ -37,7 +39,7 @@ void EditorController::setScript(Script script) {
bool EditorController::handleEvent(Ion::Events::Event event) {
if (event == Ion::Events::OK || event == Ion::Events::Back) {
Script::ErrorStatus err = m_script.writeContent(m_areaBuffer, strlen(m_areaBuffer)+1);
if (err == Script::ErrorStatus::NoEnoughSpaceAvailable) {
if (err == Script::ErrorStatus::NoEnoughSpaceAvailable || err == Script::ErrorStatus::RecordDoesNotExist) {
assert(false); // This should not happen as we set the text area according to the available space in the Kallax
} else {
stackController()->pop();

View File

@@ -39,7 +39,6 @@ MenuController::MenuController(Responder * parentResponder, ScriptStore * script
m_scriptCells[i].setParentResponder(&m_selectableTableView);
m_scriptCells[i].editableTextCell()->textField()->setDelegate(this);
m_scriptCells[i].editableTextCell()->textField()->setDraftTextBuffer(m_draftTextBuffer);
m_scriptCells[i].editableTextCell()->textField()->setTextBufferSize(Script::k_nameSize);
m_scriptCells[i].editableTextCell()->textField()->setAlignment(0.0f, 0.5f);
m_scriptCells[i].editableTextCell()->setMargins(0, 0, 0, Metric::HistoryHorizontalMargin);
}
@@ -127,9 +126,8 @@ void MenuController::renameSelectedScript() {
void MenuController::deleteScript(Script script) {
assert(!script.isNull());
script.remove();
script.destroy();
updateAddScriptRowDisplay();
m_selectableTableView.reloadData();
}
void MenuController::reloadConsole() {
@@ -302,7 +300,7 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char
} else {
newName = text;
}
Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).rename(newName);
Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).setName(newName);
if (error == Script::ErrorStatus::None) {
updateAddScriptRowDisplay();
textField->setText(newName);
@@ -317,19 +315,15 @@ bool MenuController::textFieldDidFinishEditing(TextField * textField, const char
app()->setFirstResponder(&m_selectableTableView);
static_cast<AppsContainer *>(const_cast<Container *>(app()->container()))->setShiftAlphaStatus(Ion::Events::ShiftAlphaStatus::Default);
return true;
} else if (error == Script::ErrorStatus::NameTaken) {
app()->displayWarning(I18n::Message::NameTaken);
} else if (error == Script::ErrorStatus::NonCompliantName) {
app()->displayWarning(I18n::Message::NonCompliantName);
} else {
assert(error == Script::ErrorStatus::NameTaken);
// The name cannot be to long as the text field size was set accordingly
// TODO:
// 2 solutions:
// 1. display a warning with
// app()->displayWarning(I18n::Message::ForbiddenValue);
// But that have to be done in textFieldDidReceiveEvent when textFieldShouldFinishEditing to avoid losing edition
// 2. Modify the name to take another one (add a number for instance).
// This would raise issues about the available space? We can't always add a character...
return false;
assert(error == Script::ErrorStatus::NoEnoughSpaceAvailable);
app()->displayWarning(I18n::Message::NameTooLong);
}
return false;
}
bool MenuController::textFieldDidAbortEditing(TextField * textField, const char * text) {
@@ -337,10 +331,15 @@ bool MenuController::textFieldDidAbortEditing(TextField * textField, const char
// The previous text was an empty name. Use a numbered default script name.
char numberedDefaultName[k_defaultScriptNameMaxSize];
numberedDefaultScriptName(numberedDefaultName);
Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).rename(numberedDefaultName);
Script::ErrorStatus error = m_scriptStore->scriptAtIndex(m_selectableTableView.selectedRow()).setName(numberedDefaultName);
if (error != Script::ErrorStatus::None) {
assert(false);
/* Because we use the numbered default name, the name should not be
* already taken. Plus, the script could be added only if the storage has
* enough available space to add a script named 'script99.py' */
}
assert(error == Script::ErrorStatus::None);
updateAddScriptRowDisplay();
m_selectableTableView.reloadData();
}
m_selectableTableView.selectCellAtLocation(m_selectableTableView.selectedColumn(), m_selectableTableView.selectedRow());
app()->setFirstResponder(&m_selectableTableView);
@@ -357,13 +356,13 @@ bool MenuController::textFieldDidHandleEvent(TextField * textField, bool returnV
}
void MenuController::addScript() {
if (m_scriptStore->addNewScript()) {
Script::ErrorStatus error = m_scriptStore->addNewScript();
if (error == Script::ErrorStatus::None) {
updateAddScriptRowDisplay();
m_selectableTableView.reloadData();
renameSelectedScript();
return;
}
m_selectableTableView.reloadData();
assert(false); // Adding a new script is called when !m_scriptStore.isFull() which guarantees that the available space in the storage is big enough
}
void MenuController::configureScript() {
@@ -419,6 +418,7 @@ void MenuController::intToText(int i, char * buffer) {
void MenuController::updateAddScriptRowDisplay() {
m_shouldDisplayAddScriptRow = !m_scriptStore->isFull();
m_selectableTableView.reloadData();
}
}

View File

@@ -90,6 +90,9 @@ private:
class EvenOddEditableTextCell : public ::EvenOddEditableTextCell {
public:
Responder * responder() override {
if (editableTextCell()->textField()->isEditing()) {
return this;
}
return nullptr;
}
};
@@ -97,7 +100,7 @@ private:
/* In the initializer list of the MenuController constructor, we initialize
* m_scriptCells by copying k_maxNumberOfDisplayableScriptCells times the
* constructor of an EvenOddEditableTextCell. */
char m_draftTextBuffer[Script::k_nameSize];
char m_draftTextBuffer[TextField::maxBufferSize()];
EvenOddCellWithEllipsis m_scriptParameterCells[k_maxNumberOfDisplayableScriptCells];
Shared::NewFunctionCell m_addNewScriptCell;
EvenOddCell m_emptyCell;

View File

@@ -9,29 +9,30 @@ Script::Script(Record f) :
bool Script::importationStatus() const {
assert(!isNull());
const char * body = read();
return (body[0] == 1);
Data d = value();
return (((char *)d.buffer)[0] == 1);
}
void Script::toggleImportationStatus() {
assert(bodySize() >= 1);
m_body[0] = importationStatus() ? 0 : 1;
Data d = value();
((char *)d.buffer)[0] = (((char *)d.buffer)[0] == 1 ? 0 : 1);
setValue(d);
}
const char * Script::readContent() const {
const char * body = read();
return body+k_importationStatusSize;
assert(!isNull());
Data d = value();
return (const char *)d.buffer+k_importationStatusSize;
}
Script::ErrorStatus Script::writeContent(const char * data, size_t size) {
int deltaSize = (int)size+k_importationStatusSize - (int)bodySize();
if (Ion::Kallax::sharedKallax()->moveNextRecord(start(), deltaSize)) {
*m_size += deltaSize;
strlcpy(m_body+k_importationStatusSize, data, size);
return ErrorStatus::None;
} else {
return ErrorStatus::NoEnoughSpaceAvailable;
}
Ion::Storage::Record::ErrorStatus Script::writeContent(const char * data, size_t size) {
// TODO: could we avoid this useless allocation?
char * buffer = new char[size+k_importationStatusSize];
strlcpy(buffer+1, data, size);
buffer[0] = importationStatus() ? 1 : 0;
ErrorStatus e = setValue({.buffer= buffer, .size = size+k_importationStatusSize});
delete[] buffer;
return e;
}
}

View File

@@ -5,13 +5,12 @@
namespace Code {
/* Record : | Total Size | Type | Name | Body |
* Script: | AutoImportationStatus | Content |*/
/* Record : | Total Size | Name | Body |
* Script: | AutoImportationStatus | Content |*/
class Script : public Ion::Record {
friend class ScriptStore;
class Script : public Ion::Storage::Record {
public:
Script(Ion::Record f);
Script(Ion::Storage::Record r);
bool importationStatus() const;
void toggleImportationStatus();
@@ -19,7 +18,6 @@ public:
const char * readContent() const;
ErrorStatus writeContent(const char * data, size_t size);
private:
constexpr static size_t k_importationStatusSize = 1;
};

View File

@@ -12,7 +12,7 @@ ScriptParameterController::ScriptParameterController(Responder * parentResponder
m_deleteScript(I18n::Message::DeleteScript),
m_selectableTableView(this, this, 0, 1, Metric::CommonTopMargin, Metric::CommonRightMargin,
Metric::CommonBottomMargin, Metric::CommonLeftMargin, this),
m_script(Ion::Record()),
m_script(Ion::Storage::Record()),
m_menuController(menuController)
{
}
@@ -22,7 +22,7 @@ void ScriptParameterController::setScript(Script script){
}
void ScriptParameterController::dismissScriptParameterController() {
m_script = Script(Ion::Record());
m_script = Script(Ion::Storage::Record());
stackViewController()->pop();
}

View File

@@ -19,32 +19,14 @@ ScriptStore::ScriptStore()
addScriptFromTemplate(ScriptTemplate::Polynomial());
}
Script ScriptStore::scriptAtIndex(int index) {
Ion::Record f = Ion::Kallax::sharedKallax()->recordOfTypeAtIndex(Ion::Record::Type::Script, index);
return Script(f);
}
Script ScriptStore::scriptNamed(const char * name) {
Ion::Record f = Ion::Kallax::sharedKallax()->getRecord(Ion::Record::Type::Script, name);
return Script(f);
}
int ScriptStore::numberOfScripts() {
return Ion::Kallax::sharedKallax()->numberOfRecordOfType(Ion::Record::Type::Script);
}
bool ScriptStore::addNewScript() {
return addScriptFromTemplate(ScriptTemplate::Empty());
}
void ScriptStore::deleteAllScripts() {
for (int i = 0; i < numberOfScripts(); i++) {
scriptAtIndex(i).remove();
scriptAtIndex(i).destroy();
}
}
bool ScriptStore::isFull() {
return (numberOfScripts() >= k_maxNumberOfScripts || Ion::Kallax::sharedKallax()->availableSize() < k_fullFreeSpaceSizeLimit);
return (numberOfScripts() >= k_maxNumberOfScripts || storage.availableSize() < k_fullFreeSpaceSizeLimit);
}
void ScriptStore::scanScriptsForFunctionsAndVariables(void * context, ScanCallback storeFunction, ScanCallback storeVariable) {
@@ -137,18 +119,15 @@ const char * ScriptStore::contentOfScript(const char * name) {
return script.readContent();
}
bool ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) {
Script::ErrorStatus ScriptStore::addScriptFromTemplate(const ScriptTemplate * scriptTemplate) {
size_t scriptSize = strlen(scriptTemplate->content())+1;
char * body = new char[scriptSize+Script::k_importationStatusSize];
body[0] = 1;
strlcpy(body+1, scriptTemplate->content(), scriptSize);
bool result = false;
if (Ion::Kallax::sharedKallax()->sizeOfRecordWithBody(body) <= Ion::Kallax::sharedKallax()->availableSize()) {
Ion::Kallax::sharedKallax()->addRecord(scriptTemplate->name(), Ion::Record::Type::Script, body);
result = true;
}
strlcpy(body+Script::k_importationStatusSize, scriptTemplate->content(), scriptSize);
Script::ErrorStatus err = storage.createRecord(scriptTemplate->name(), body, scriptSize+Script::k_importationStatusSize);
assert(err != Script::ErrorStatus::NonCompliantName);
delete[] body;
return result;
return err;
}
const char * ScriptStore::structID(mp_parse_node_struct_t *structNode) {

View File

@@ -8,6 +8,8 @@ extern "C" {
#include "py/parse.h"
}
extern Ion::Storage storage;
namespace Code {
class ScriptStore : public MicroPython::ScriptProvider {
@@ -17,10 +19,18 @@ public:
static constexpr int k_maxNumberOfScripts = 8;
ScriptStore();
Script scriptAtIndex(int index);
Script scriptNamed(const char * name);
int numberOfScripts();
bool addNewScript();
Script scriptAtIndex(int index) {
return Script(storage.recordWithExtensionAtIndex(k_scriptExtension, index));
}
Script scriptNamed(const char * name) {
return Script(storage.recordNamed(name));
}
int numberOfScripts() {
return storage.numberOfRecordsWithExtension(k_scriptExtension);
}
Ion::Storage::Record::ErrorStatus addNewScript() {
return addScriptFromTemplate(ScriptTemplate::Empty());
}
void deleteAllScripts();
bool isFull();
@@ -31,11 +41,15 @@ public:
/* MicroPython::ScriptProvider */
const char * contentOfScript(const char * name) override;
bool addScriptFromTemplate(const ScriptTemplate * scriptTemplate);
Ion::Storage::Record::ErrorStatus addScriptFromTemplate(const ScriptTemplate * scriptTemplate);
private:
// If the kallax free space has a size smaller than
// k_fullFreeSpaceSizeLimit, we consider the script store as full.
static constexpr int k_fullFreeSpaceSizeLimit = Ion::Record::k_sizeSize+Ion::Record::k_nameSize+Ion::Record::k_typeSize+10;
/* If the storage available space has a smaller size than
* k_fullFreeSpaceSizeLimit, we consider the script store as full.
* To be able to add a new empty record, the available space should at least
* stores a Script with default name "script99.py" (12 char), the importation
* status (1 char), the default content "from math import *\n" (20 char) and
* 10 char of free space. */
static constexpr int k_fullFreeSpaceSizeLimit = sizeof(Ion::Storage::record_size_t)+12+1+20+10;
static constexpr size_t k_fileInput2ParseNodeStructKind = 1;
static constexpr size_t k_functionDefinitionParseNodeStructKind = 3;
static constexpr size_t k_expressionStatementParseNodeStructKind = 5;

View File

@@ -12,7 +12,6 @@ public:
float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor = KDColorWhite);
void setDelegate(TextFieldDelegate * delegate) { m_delegate = delegate; }
void setDraftTextBuffer(char * draftTextBuffer);
void setTextBufferSize(size_t size);
bool isEditing() const;
size_t draftTextLength() const;
void setText(const char * text);
@@ -31,7 +30,6 @@ protected:
public:
ContentView(char * textBuffer, char * draftTextBuffer, size_t textBufferSize, KDText::FontSize size, float horizontalAlignment = 0.0f, float verticalAlignment = 0.5f, KDColor textColor = KDColorBlack, KDColor = KDColorWhite);
void setDraftTextBuffer(char * draftTextBuffer);
void setTextBufferSize(size_t size);
void drawRect(KDContext * ctx, KDRect rect) const override;
bool isEditing() const { return m_isEditing; }
const char * text() const override;

View File

@@ -22,10 +22,6 @@ void TextField::ContentView::setDraftTextBuffer(char * draftTextBuffer) {
m_draftTextBuffer = draftTextBuffer;
}
void TextField::ContentView::setTextBufferSize(size_t size) {
m_textBufferSize = size;
}
void TextField::ContentView::drawRect(KDContext * ctx, KDRect rect) const {
KDColor bckCol = m_backgroundColor;
if (m_isEditing) {
@@ -176,10 +172,6 @@ void TextField::setDraftTextBuffer(char * draftTextBuffer) {
m_contentView.setDraftTextBuffer(draftTextBuffer);
}
void TextField::setTextBufferSize(size_t size) {
m_contentView.setTextBufferSize(size);
}
bool TextField::isEditing() const {
return m_contentView.isEditing();
}

View File

@@ -18,9 +18,8 @@ ion/src/shared/platform_info.o: SFLAGS += -DPATCH_LEVEL="$(call initializer_list
objs += $(addprefix ion/src/shared/, \
events.o \
kallax.o \
record.o \
platform_info.o \
storage.o \
)
tests += $(addprefix ion/test/,\

View File

@@ -7,11 +7,10 @@
#include <ion/console.h>
#include <ion/display.h>
#include <ion/events.h>
#include <ion/kallax.h>
#include <ion/keyboard.h>
#include <ion/led.h>
#include <ion/power.h>
#include <ion/record.h>
#include <ion/storage.h>
#include <ion/usb.h>
#include <stdint.h>
#include <string.h>

View File

@@ -1,44 +0,0 @@
#ifndef ION_KALLAX_H
#define ION_KALLAX_H
#include <ion/record.h>
/* Kallax : | Magic | Record1 | Record2 | ... | Magic |
* | Magic | Size1 | Type1 | Name1 | BodySize1 | Body1 | Size2 | Type2 | Name2 | BodySize2 | Body2 | ... | Magic */
namespace Ion {
class Kallax {
public:
Kallax();
static Kallax * sharedKallax();
int numberOfRecordOfType(Record::Type type);
Record recordOfTypeAtIndex(Record::Type type, int index);
Record getRecord(Record::Type type, const char * name);
Record addRecord(const char * name, Record::Type type, const char * content);
// availableSize takes into account the the size of the last Record must be 0.
size_t availableSize();
bool isNameTaken(const char * name, Record::Type type);
bool moveNextRecord(char * start, int delta);
size_t sizeOfRecordWithBody(const char * body) const;
private:
// lastUsedData takes into account the the size of the last Record must be 0.
char * lastUsedData();
size_t * sizeAddressOfRecordStarting(char * start) const;
size_t sizeOfRecordStarting(char * start) const;
Record::Type typeOfRecordStarting(char * start) const;
char * nameOfRecordStarting(char * start);
char * bodyOfRecordStarting(char * start);
constexpr static size_t k_totalSize = 4096;
constexpr static uint32_t Magic = 0xDECA0DF0;
uint32_t m_dataHeader;
char m_data[k_totalSize];
uint32_t m_dataFooter;
};
}
#endif

View File

@@ -1,51 +0,0 @@
#ifndef ION_RECORD_H
#define ION_RECORD_H
#include <stdint.h>
#include <stddef.h>
/* Record : | Total Size | Type | Name | Body | */
namespace Ion {
class Record {
public:
enum class Type : uint8_t {
Null,
Script
};
enum class ErrorStatus {
None = 0,
NameTaken = 1,
NameTooLong = 2,
NoEnoughSpaceAvailable = 3
};
Record(size_t * totalSize = nullptr, char * name = nullptr, Type type = Type::Null, char * body = nullptr);
bool isNull() const;
const char * name() const;
ErrorStatus rename(const char * newName); // May fail if name taken or name too long
const char * read() const;
ErrorStatus write(const char * data, size_t size = 0); // May fail if no more space is available
Type type();
void remove(); // Will always succeed
constexpr static size_t k_nameSize = 50;
constexpr static size_t k_sizeSize = sizeof(size_t);
constexpr static size_t k_typeSize = sizeof(Type);
protected:
size_t bodySize() const;
char * start();
char * m_body;
size_t * m_size;
private:
char * m_name;
Type m_type;
};
}
#endif

119
ion/include/ion/storage.h Normal file
View File

@@ -0,0 +1,119 @@
#ifndef ION_STORAGE_H
#define ION_STORAGE_H
#include <stddef.h>
namespace Ion {
class Storage;
}
extern Ion::Storage storage;
namespace Ion {
/* Storage : | Magic | Record1 | Record2 | ... | Magic |
* | Magic | Size1(uint16_t) | Name1 | Body1 | Size2(uint16_t) | Name2 | Body2 | ... | Magic */
class Storage {
public:
class Record {
friend class Storage;
public:
enum class ErrorStatus {
None = 0,
NameTaken = 1,
NonCompliantName = 2,
NoEnoughSpaceAvailable = 3,
RecordDoesNotExist = 4
};
struct Data
{
const void * buffer;
size_t size;
};
Record(const char * name = nullptr);
bool operator==(const Record & other) const {
return m_nameCRC32 == other.m_nameCRC32;
}
bool isNull() const {
return m_nameCRC32 == 0;
}
const char * name() const {
return storage.nameOfRecord(*this);
}
ErrorStatus setName(const char * name) {
return storage.setNameOfRecord(*this, name);
}
Data value() const {
return storage.valueOfRecord(*this);
}
ErrorStatus setValue(Data data) {
return storage.setValueOfRecord(*this, data);
}
void destroy() {
return storage.destroyRecord(*this);
}
private:
uint32_t m_nameCRC32;
};
Storage();
size_t availableSize();
Record::ErrorStatus createRecord(const char * name, const void * data, size_t size);
int numberOfRecordsWithExtension(const char * extension);
Record recordWithExtensionAtIndex(const char * extension, int index);
Record recordNamed(const char * name);
typedef uint16_t record_size_t;
private:
constexpr static uint32_t Magic = 0xDECA0DF0;
constexpr static size_t k_storageSize = 4096;
constexpr static size_t k_maxRecordSize = (1 << sizeof(record_size_t)*8);
/* Getters/Setters on recordID */
const char * nameOfRecord(const Record record);
Record::ErrorStatus setNameOfRecord(const Record record, const char * name);
Record::Data valueOfRecord(const Record record);
Record::ErrorStatus setValueOfRecord(const Record record, Record::Data data);
void destroyRecord(const Record record);
/* Getters on address in buffer */
record_size_t sizeOfRecordStarting(char * start) const;
const char * nameOfRecordStarting(char * start) const;
const void * valueOfRecordStarting(char * start) const;
/* Overriders */
size_t overrideSizeAtPosition(char * position, record_size_t size);
size_t overrideNameAtPosition(char * position, const char * name);
size_t overrideValueAtPosition(char * position, const void * data, record_size_t size);
bool isNameTaken(const char * name, Record * recordToExclude = nullptr);
bool nameCompliant(const char * name) const;
char * endBuffer();
size_t sizeOfRecord(const char * name, 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); };
uint32_t m_magicHeader;
char m_buffer[k_storageSize];
uint32_t m_magicFooter;
};
}
#endif

View File

@@ -1,156 +0,0 @@
#include <ion/kallax.h>
#include <string.h>
#include <assert.h>
Ion::Kallax kallax;
namespace Ion {
Kallax::Kallax() :
m_dataHeader(Magic),
m_data(),
m_dataFooter(Magic)
{
size_t * p = (size_t *)m_data;
p[0] = 0;
}
Kallax * Kallax::sharedKallax() {
return &kallax;
}
int Kallax::numberOfRecordOfType(Record::Type type) {
assert(m_dataHeader == Magic);
assert(m_dataFooter == Magic);
int count = 0;
char * currentPointer = m_data;
size_t size = sizeOfRecordStarting(currentPointer);
while (size != 0 && currentPointer < m_data + k_totalSize) {
if (typeOfRecordStarting(currentPointer) == type) {
count++;
}
currentPointer += size;
size = sizeOfRecordStarting(currentPointer);
}
return count;
}
Record Kallax::recordOfTypeAtIndex(Record::Type type, int index) {
int currentIndex = -1;
char * currentPointer = m_data;
size_t size = sizeOfRecordStarting(currentPointer);
while (size != 0 && currentPointer < m_data + k_totalSize) {
if (typeOfRecordStarting(currentPointer) == type) {
currentIndex++;
if (currentIndex == index) {
break;
}
}
currentPointer += size;
size = sizeOfRecordStarting(currentPointer);
}
return Record(sizeAddressOfRecordStarting(currentPointer), nameOfRecordStarting(currentPointer), type, bodyOfRecordStarting(currentPointer));
}
Record Kallax::getRecord(Record::Type type, const char * name) {
for (int i = 0; i < numberOfRecordOfType(type); i++) {
Record currentRecord = recordOfTypeAtIndex(type, i);
if (strcmp(currentRecord.name(), name) == 0) {
return currentRecord;
}
}
return Record();
}
Record Kallax::addRecord(const char * name, Record::Type type, const char * body) {
// assert name is short enough and there is enough space to add the record
assert(strlen(name) < Record::k_nameSize);
assert(availableSize() >= sizeOfRecordWithBody(body));
// Find the end of data
char * currentPointer = m_data;
size_t size = sizeOfRecordStarting(currentPointer);
while (size != 0 && currentPointer < m_data + k_totalSize) {
currentPointer += size;
size = sizeOfRecordStarting(currentPointer);
}
size_t recordSize = sizeOfRecordWithBody(body);
// Fill totalSize
*((size_t *)currentPointer) = recordSize;
// Fill type
*(currentPointer+Record::k_sizeSize) = (uint8_t)type;
// Fill name
strlcpy(currentPointer+Record::k_sizeSize+Record::k_typeSize, name, Record::k_nameSize);
// Fill body
strlcpy(currentPointer+Record::k_sizeSize+Record::k_typeSize+Record::k_nameSize, body, strlen(body)+1);
char * nextPointer = currentPointer + recordSize;
*((size_t *)nextPointer) = 0;
return Record(sizeAddressOfRecordStarting(currentPointer), nameOfRecordStarting(currentPointer), type, bodyOfRecordStarting(currentPointer));
}
char * Kallax::lastUsedData() {
size_t usedSize = 0;
char * currentPointer = m_data;
size_t size = sizeOfRecordStarting(currentPointer);
while (size != 0 && currentPointer < m_data + k_totalSize) {
usedSize += size;
currentPointer += size;
size = sizeOfRecordStarting(currentPointer);
}
return currentPointer + Record::k_sizeSize;
}
size_t Kallax::availableSize() {
return k_totalSize-(lastUsedData()-m_data);
}
bool Kallax::isNameTaken(const char * name, Record::Type type) {
char * currentPointer = m_data;
size_t size = sizeOfRecordStarting(currentPointer);
while (size != 0 && currentPointer < m_data + k_totalSize) {
if (typeOfRecordStarting(currentPointer) == type && strcmp(nameOfRecordStarting(currentPointer), name) == 0) {
return true;
}
currentPointer += size;
size = sizeOfRecordStarting(currentPointer);
}
return false;
}
bool Kallax::moveNextRecord(char * start, int delta) {
if (delta > (int)availableSize()) {
return false;
}
char * nextRecord = start + sizeOfRecordStarting(start);
memmove(nextRecord+delta, nextRecord, lastUsedData()-nextRecord);
return true;
}
size_t * Kallax::sizeAddressOfRecordStarting(char * start) const {
return (size_t *)start;
}
size_t Kallax::sizeOfRecordStarting(char * start) const {
if (start >= m_data + k_totalSize) {
return 0;
}
return *(sizeAddressOfRecordStarting(start));
}
Record::Type Kallax::typeOfRecordStarting(char * start) const {
return (Record::Type)*((uint8_t *)start+Record::k_sizeSize);
}
char * Kallax::nameOfRecordStarting(char * start) {
return start+Record::k_sizeSize+Record::k_typeSize;
}
char * Kallax::bodyOfRecordStarting(char * start) {
return start+Record::k_sizeSize+Record::k_typeSize+Record::k_nameSize;
}
size_t Kallax::sizeOfRecordWithBody(const char * body) const {
return Record::k_sizeSize+Record::k_typeSize+Record::k_nameSize+strlen(body)+1;
}
}

View File

@@ -20,7 +20,7 @@
#define FORCE_LINK
#endif
extern Ion::Kallax kallax;
extern Ion::Storage storage;
class PlatformInfo {
public:
@@ -28,14 +28,16 @@ public:
m_header(Magic),
m_version{EPSILON_VERSION},
m_patchLevel{PATCH_LEVEL},
m_storageAddress(&kallax),
m_storageAddress(&storage),
m_footer(Magic) { }
const char * version() const {
assert(m_storageAddress != nullptr);
assert(m_header == Magic);
assert(m_footer == Magic);
return m_version;
}
const char * patchLevel() const {
assert(m_storageAddress != nullptr);
assert(m_header == Magic);
assert(m_footer == Magic);
return m_patchLevel;

View File

@@ -1,71 +0,0 @@
#include <ion/record.h>
#include <ion/kallax.h>
#include <string.h>
#include <assert.h>
namespace Ion {
Record::Record(size_t * size, char * name, Type type, char * body) :
m_body(body),
m_size(size),
m_name(name),
m_type(type)
{
}
bool Record::isNull() const {
if (m_type == Type::Null) {
assert(m_size == nullptr);
return true;
}
return false;
}
const char * Record::name() const {
return m_name;
}
Record::ErrorStatus Record::rename(const char * newName) {
if (Kallax::sharedKallax()->isNameTaken(newName, m_type)) {
return ErrorStatus::NameTaken;
}
if (strlen(newName) >= k_nameSize) {
return ErrorStatus::NameTooLong;
}
strlcpy(m_name, newName, k_nameSize);
return ErrorStatus::None;
}
const char * Record::read() const {
return m_body;
}
Record::ErrorStatus Record::write(const char * data, size_t size) {
int deltaSize = (int)size - (int)bodySize();
// TODO: if this fails because deltaSize is too big, return an error?
if (Kallax::sharedKallax()->moveNextRecord(start(), deltaSize)) {
*m_size += deltaSize;
strlcpy(m_body, data, size);
return ErrorStatus::None;
}
return ErrorStatus::NoEnoughSpaceAvailable;
}
Record::Type Record::type() {
return m_type;
}
void Record::remove() {
Kallax::sharedKallax()->moveNextRecord(start(), -*(m_size));
}
char * Record::start() {
return m_name - k_typeSize - k_sizeSize;
}
size_t Record::bodySize() const {
return *m_size - k_nameSize - k_typeSize - k_sizeSize;
}
}

270
ion/src/shared/storage.cpp Normal file
View File

@@ -0,0 +1,270 @@
#include <ion.h>
#include <string.h>
#include <assert.h>
Ion::Storage storage;
namespace Ion {
Storage::Record::Record(const char * name) {
if (name == nullptr) {
m_nameCRC32 = 0;
return;
}
size_t lenght = strlen(name);
size_t crc32InputSize = lenght*sizeof(char)/sizeof(uint32_t)+1;
uint32_t * crc32Input = new uint32_t[crc32InputSize];
memset(crc32Input, 0, crc32InputSize*sizeof(uint32_t));
strlcpy((char *)crc32Input, name, lenght+1);
assert((crc32InputSize*sizeof(uint32_t) & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4
m_nameCRC32 = Ion::crc32(crc32Input, crc32InputSize);
}
Storage::Storage() :
m_magicHeader(Magic),
m_buffer(),
m_magicFooter(Magic)
{
assert(m_magicHeader == Magic);
assert(m_magicFooter == Magic);
// Set the size of the first record to 0
overrideSizeAtPosition(m_buffer, 0);
}
size_t Storage::availableSize() {
return k_storageSize-(endBuffer()-m_buffer)-sizeof(record_size_t);
}
Storage::Record::ErrorStatus Storage::createRecord(const char * name, const void * data, size_t size) {
if (!nameCompliant(name)) {
return Record::ErrorStatus::NonCompliantName;
}
size_t recordSize = sizeOfRecord(name, size);
if (recordSize >= k_maxRecordSize || recordSize > availableSize()) {
return Record::ErrorStatus::NoEnoughSpaceAvailable;
}
if (isNameTaken(name)) {
return Record::ErrorStatus::NameTaken;
}
// Find the end of data
char * newRecord = endBuffer();
// Fill totalSize
newRecord += overrideSizeAtPosition(newRecord, (record_size_t)recordSize);
// Fill name
newRecord += overrideNameAtPosition(newRecord, name);
// Fill data
newRecord += overrideValueAtPosition(newRecord, data, size);
// Next Record is null-sized
overrideSizeAtPosition(newRecord, 0);
return Record::ErrorStatus::None;
}
int Storage::numberOfRecordsWithExtension(const char * extension) {
int count = 0;
for (char * p : *this) {
const char * name = nameOfRecordStarting(p);
const char * ext = name+strlen(name)-strlen(extension);
if (strcmp(ext, extension) == 0) {
count++;
}
}
return count;
}
Storage::Record Storage::recordWithExtensionAtIndex(const char * extension, int index) {
int currentIndex = -1;
const char * name = nullptr;
for (char * p : *this) {
const char * currentName = nameOfRecordStarting(p);
const char * currentExtension = currentName+strlen(currentName)-strlen(extension);
if (strcmp(currentExtension, extension) == 0) {
currentIndex++;
}
if (currentIndex == index) {
name = currentName;
break;
}
}
if (name == nullptr) {
return Record();
}
return Record(name);
}
Storage::Record Storage::recordNamed(const char * name) {
for (char * p : *this) {
const char * currentName = nameOfRecordStarting(p);
if (strcmp(currentName, name) == 0) {
return Record(name);
}
}
return Record();
}
const char * Storage::nameOfRecord(const Record record) {
for (char * p : *this) {
Record currentRecord(nameOfRecordStarting(p));
if (record == currentRecord) {
return nameOfRecordStarting(p);
}
}
return nullptr;
}
Storage::Record::ErrorStatus Storage::setNameOfRecord(Record record, const char * name) {
if (!nameCompliant(name)) {
return Record::ErrorStatus::NonCompliantName;
}
if (isNameTaken(name, &record)) {
return Record::ErrorStatus::NameTaken;
}
size_t nameSize = strlen(name)+1;
for (char * p : *this) {
Record currentRecord(nameOfRecordStarting(p));
if (record == currentRecord) {
size_t previousNameSize = strlen(nameOfRecordStarting(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 Record::ErrorStatus::NoEnoughSpaceAvailable;
}
overrideSizeAtPosition(p, newRecordSize);
overrideNameAtPosition(p+sizeof(record_size_t), name);
return Record::ErrorStatus::None;
}
}
return Record::ErrorStatus::RecordDoesNotExist;
}
Storage::Record::Data Storage::valueOfRecord(const Record record) {
for (char * p : *this) {
Record currentRecord(nameOfRecordStarting(p));
if (record == currentRecord) {
const char * name = nameOfRecordStarting(p);
record_size_t size = sizeOfRecordStarting(p);
const void * value = valueOfRecordStarting(p);
return {.buffer= value, .size= size-strlen(name)-1-sizeof(record_size_t)};
}
}
return {.buffer= nullptr, .size= 0};
}
Storage::Record::ErrorStatus Storage::setValueOfRecord(Record record, Record::Data data) {
for (char * p : *this) {
Record currentRecord(nameOfRecordStarting(p));
if (record == currentRecord) {
record_size_t previousRecordSize = sizeOfRecordStarting(p);
const char * name = nameOfRecordStarting(p);
size_t newRecordSize = sizeOfRecord(name, data.size);
if (newRecordSize >= k_maxRecordSize || !slideBuffer(p+previousRecordSize, newRecordSize-previousRecordSize)) {
return Record::ErrorStatus::NoEnoughSpaceAvailable;
}
record_size_t nameSize = strlen(name)+1;
overrideSizeAtPosition(p, newRecordSize);
overrideValueAtPosition(p+sizeof(record_size_t)+nameSize, data.buffer, data.size);
return Record::ErrorStatus::None;
}
}
return Record::ErrorStatus::RecordDoesNotExist;
}
void Storage::destroyRecord(Record record) {
for (char * p : *this) {
Record currentRecord(nameOfRecordStarting(p));
if (record == currentRecord) {
record_size_t previousRecordSize = sizeOfRecordStarting(p);
slideBuffer(p+previousRecordSize, -previousRecordSize);
}
}
}
Storage::record_size_t Storage::sizeOfRecordStarting(char * start) const {
return *((record_size_t *)start);
}
const char * Storage::nameOfRecordStarting(char * start) const {
return start+sizeof(record_size_t);
}
const void * Storage::valueOfRecordStarting(char * start) const {
char * currentChar = start+sizeof(record_size_t);
while (*currentChar != 0) {
currentChar++;
}
return currentChar+1;
}
size_t Storage::overrideSizeAtPosition(char * position, record_size_t size) {
*((record_size_t *)position) = size;
return sizeof(record_size_t);
}
size_t Storage::overrideNameAtPosition(char * position, const char * name) {
return strlcpy(position, name, strlen(name)+1)+1;
}
size_t Storage::overrideValueAtPosition(char * position, const void * data, record_size_t size) {
memcpy(position, data, size);
return size;
}
bool Storage::isNameTaken(const char * name, Record * recordToExclude) {
Record r = Record(name);
if (r == Record()) {
return true;
}
for (char * p : *this) {
Record s(nameOfRecordStarting(p));
if (recordToExclude && s == *recordToExclude) {
continue;
}
if (s == r) {
return true;
}
}
return false;
}
bool Storage::nameCompliant(const char * name) const {
/* The name format is [a-zA-Z0-9.]+ */
const char * currentChar = name;
while (*currentChar != 0) {
if ((*currentChar >= 'a' && *currentChar <= 'z') || (*currentChar >= 'A' && *currentChar <= 'Z') || (*currentChar >= '0' && *currentChar <= '9') || *currentChar == '.') {
currentChar++;
continue;
}
return false;
}
return true;
}
char * Storage::endBuffer() {
char * currentBuffer = m_buffer;
for (char * p : *this) {
currentBuffer += sizeOfRecordStarting(p);
}
return currentBuffer;
}
size_t Storage::sizeOfRecord(const char * name, size_t dataSize) const {
size_t nameSize = strlen(name)+1;
return nameSize+dataSize+sizeof(record_size_t);
}
bool Storage::slideBuffer(char * position, int delta) {
if (delta > (int)availableSize()) {
return false;
}
memmove(position+delta, position, endBuffer()+sizeof(record_size_t)-position);
return true;
}
Storage::RecordIterator & Storage::RecordIterator::operator++() {
record_size_t size = *((record_size_t *)m_recordStart);
char * nextRecord = m_recordStart+size;
record_size_t newRecordSize = *((record_size_t *)nextRecord);
m_recordStart = (newRecordSize == 0 ? nullptr : nextRecord);
return *this;
}
}