big refactoring and features added
This commit is contained in:
497
App/Services/profile_storage.c
Normal file
497
App/Services/profile_storage.c
Normal file
@ -0,0 +1,497 @@
|
||||
/**
|
||||
* @file profile_storage.c
|
||||
* @brief Streamed SD-card writer for GUI-initiated profile saves.
|
||||
*/
|
||||
|
||||
#include "profile_storage.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "app_types.h"
|
||||
#include "storage_sd.h"
|
||||
|
||||
#define PROFILE_STORAGE_PROFILES_DIR "profiles"
|
||||
#define PROFILE_STORAGE_WAVES_DIR "waves"
|
||||
#define PROFILE_STORAGE_PROFILE_FILE_TEMPLATE "profiles/PROF%04u.INI"
|
||||
#define PROFILE_STORAGE_WAVE_FILE_TEMPLATE "waves/WAVE%04u.CSV"
|
||||
#define PROFILE_STORAGE_MAX_FILE_INDEX 9999u
|
||||
#define PROFILE_STORAGE_INDEX_LINE_BUFFER_SIZE 192u
|
||||
|
||||
typedef struct profile_storage_context_t {
|
||||
bool active;
|
||||
bool profile_file_open;
|
||||
bool waveform_file_open;
|
||||
char display_name[APP_PROFILE_NAME_LENGTH];
|
||||
char profile_path[APP_PROFILE_PATH_LENGTH];
|
||||
char waveform_path[APP_PROFILE_PATH_LENGTH];
|
||||
uint16_t profile_expected_bytes;
|
||||
uint16_t waveform_expected_bytes;
|
||||
uint16_t profile_received_bytes;
|
||||
uint16_t waveform_received_bytes;
|
||||
FIL profile_file;
|
||||
FIL waveform_file;
|
||||
profile_storage_status_t last_status;
|
||||
} profile_storage_context_t;
|
||||
|
||||
static profile_storage_context_t g_profile_storage;
|
||||
|
||||
static profile_storage_status_t profile_storage_set_status(profile_storage_status_t status)
|
||||
{
|
||||
g_profile_storage.last_status = status;
|
||||
return status;
|
||||
}
|
||||
|
||||
static void profile_storage_reset_context(void)
|
||||
{
|
||||
memset(&g_profile_storage, 0, sizeof(g_profile_storage));
|
||||
g_profile_storage.last_status = PROFILE_STORAGE_STATUS_OK;
|
||||
}
|
||||
|
||||
static bool profile_storage_is_name_character_allowed(char character)
|
||||
{
|
||||
return (isalnum((unsigned char)character) != 0) ||
|
||||
(character == ' ') ||
|
||||
(character == '-') ||
|
||||
(character == '_');
|
||||
}
|
||||
|
||||
static bool profile_storage_copy_validated_name(char *destination, size_t destination_size, const char *source)
|
||||
{
|
||||
const char *start;
|
||||
const char *end;
|
||||
size_t length;
|
||||
size_t index;
|
||||
|
||||
if ((destination == NULL) || (destination_size < APP_PROFILE_NAME_LENGTH) || (source == NULL))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
start = source;
|
||||
while ((*start != '\0') && isspace((unsigned char)*start))
|
||||
{
|
||||
++start;
|
||||
}
|
||||
|
||||
end = start + strlen(start);
|
||||
while ((end > start) && isspace((unsigned char)end[-1]))
|
||||
{
|
||||
--end;
|
||||
}
|
||||
|
||||
length = (size_t)(end - start);
|
||||
if ((length == 0u) || (length > APP_PROFILE_NAME_MAX_CHARACTERS))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (index = 0u; index < length; ++index)
|
||||
{
|
||||
if (!profile_storage_is_name_character_allowed(start[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(destination, start, length);
|
||||
destination[length] = '\0';
|
||||
return true;
|
||||
}
|
||||
|
||||
static void profile_storage_clear_session_paths(void)
|
||||
{
|
||||
g_profile_storage.display_name[0] = '\0';
|
||||
g_profile_storage.profile_path[0] = '\0';
|
||||
g_profile_storage.waveform_path[0] = '\0';
|
||||
}
|
||||
|
||||
static void profile_storage_close_open_files(void)
|
||||
{
|
||||
if (g_profile_storage.waveform_file_open)
|
||||
{
|
||||
(void)storage_sd_close_file(&g_profile_storage.waveform_file);
|
||||
g_profile_storage.waveform_file_open = false;
|
||||
}
|
||||
|
||||
if (g_profile_storage.profile_file_open)
|
||||
{
|
||||
(void)storage_sd_close_file(&g_profile_storage.profile_file);
|
||||
g_profile_storage.profile_file_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void profile_storage_remove_partial_files(void)
|
||||
{
|
||||
if (g_profile_storage.waveform_path[0] != '\0')
|
||||
{
|
||||
(void)storage_sd_remove_file(g_profile_storage.waveform_path);
|
||||
}
|
||||
|
||||
if (g_profile_storage.profile_path[0] != '\0')
|
||||
{
|
||||
(void)storage_sd_remove_file(g_profile_storage.profile_path);
|
||||
}
|
||||
}
|
||||
|
||||
static void profile_storage_abort_session(void)
|
||||
{
|
||||
profile_storage_close_open_files();
|
||||
profile_storage_remove_partial_files();
|
||||
g_profile_storage.active = false;
|
||||
g_profile_storage.profile_expected_bytes = 0u;
|
||||
g_profile_storage.waveform_expected_bytes = 0u;
|
||||
g_profile_storage.profile_received_bytes = 0u;
|
||||
g_profile_storage.waveform_received_bytes = 0u;
|
||||
profile_storage_clear_session_paths();
|
||||
}
|
||||
|
||||
static profile_storage_status_t profile_storage_format_target_path(char *destination,
|
||||
size_t destination_size,
|
||||
const char *format_string,
|
||||
uint16_t file_index)
|
||||
{
|
||||
int written;
|
||||
|
||||
written = snprintf(destination, destination_size, format_string, (unsigned int)file_index);
|
||||
if ((written <= 0) || ((size_t)written >= destination_size))
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_FILE_NAME_EXHAUSTED);
|
||||
}
|
||||
|
||||
return PROFILE_STORAGE_STATUS_OK;
|
||||
}
|
||||
|
||||
static profile_storage_status_t profile_storage_find_free_paths(uint16_t waveform_text_bytes)
|
||||
{
|
||||
uint16_t file_index;
|
||||
|
||||
for (file_index = 1u; file_index <= PROFILE_STORAGE_MAX_FILE_INDEX; ++file_index)
|
||||
{
|
||||
FILINFO file_info;
|
||||
FRESULT result;
|
||||
profile_storage_status_t status;
|
||||
|
||||
status = profile_storage_format_target_path(g_profile_storage.profile_path,
|
||||
sizeof(g_profile_storage.profile_path),
|
||||
PROFILE_STORAGE_PROFILE_FILE_TEMPLATE,
|
||||
file_index);
|
||||
if (status != PROFILE_STORAGE_STATUS_OK)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
result = storage_sd_stat(g_profile_storage.profile_path, &file_info);
|
||||
if (result == FR_OK)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ((result != FR_NO_FILE) && (result != FR_NO_PATH))
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_STORAGE_UNAVAILABLE);
|
||||
}
|
||||
|
||||
if (waveform_text_bytes > 0u)
|
||||
{
|
||||
status = profile_storage_format_target_path(g_profile_storage.waveform_path,
|
||||
sizeof(g_profile_storage.waveform_path),
|
||||
PROFILE_STORAGE_WAVE_FILE_TEMPLATE,
|
||||
file_index);
|
||||
if (status != PROFILE_STORAGE_STATUS_OK)
|
||||
{
|
||||
return status;
|
||||
}
|
||||
|
||||
result = storage_sd_stat(g_profile_storage.waveform_path, &file_info);
|
||||
if (result == FR_OK)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if ((result != FR_NO_FILE) && (result != FR_NO_PATH))
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_STORAGE_UNAVAILABLE);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g_profile_storage.waveform_path[0] = '\0';
|
||||
}
|
||||
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_OK);
|
||||
}
|
||||
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_FILE_NAME_EXHAUSTED);
|
||||
}
|
||||
|
||||
static profile_storage_status_t profile_storage_open_target_files(void)
|
||||
{
|
||||
FRESULT result;
|
||||
|
||||
result = storage_sd_make_directory(PROFILE_STORAGE_PROFILES_DIR);
|
||||
if (result != FR_OK)
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_DIRECTORY_ERROR);
|
||||
}
|
||||
|
||||
if (g_profile_storage.waveform_expected_bytes > 0u)
|
||||
{
|
||||
result = storage_sd_make_directory(PROFILE_STORAGE_WAVES_DIR);
|
||||
if (result != FR_OK)
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_DIRECTORY_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
result = storage_sd_open_file(&g_profile_storage.profile_file,
|
||||
g_profile_storage.profile_path,
|
||||
FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (result != FR_OK)
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_FILE_OPEN_ERROR);
|
||||
}
|
||||
g_profile_storage.profile_file_open = true;
|
||||
|
||||
if (g_profile_storage.waveform_expected_bytes > 0u)
|
||||
{
|
||||
result = storage_sd_open_file(&g_profile_storage.waveform_file,
|
||||
g_profile_storage.waveform_path,
|
||||
FA_CREATE_ALWAYS | FA_WRITE);
|
||||
if (result != FR_OK)
|
||||
{
|
||||
profile_storage_abort_session();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_FILE_OPEN_ERROR);
|
||||
}
|
||||
g_profile_storage.waveform_file_open = true;
|
||||
}
|
||||
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_OK);
|
||||
}
|
||||
|
||||
static profile_storage_status_t profile_storage_write_to_file(FIL *file,
|
||||
uint16_t *received_bytes,
|
||||
uint16_t expected_bytes,
|
||||
const uint8_t *data,
|
||||
uint16_t data_size)
|
||||
{
|
||||
UINT bytes_written = 0u;
|
||||
FRESULT result;
|
||||
|
||||
if ((file == NULL) || (received_bytes == NULL) || (data == NULL) || (data_size == 0u))
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if ((uint32_t)(*received_bytes) + (uint32_t)data_size > (uint32_t)expected_bytes)
|
||||
{
|
||||
profile_storage_abort_session();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_SIZE_MISMATCH);
|
||||
}
|
||||
|
||||
result = f_write(file, data, data_size, &bytes_written);
|
||||
if ((result != FR_OK) || (bytes_written != data_size))
|
||||
{
|
||||
profile_storage_abort_session();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_WRITE_ERROR);
|
||||
}
|
||||
|
||||
*received_bytes = (uint16_t)(*received_bytes + data_size);
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_OK);
|
||||
}
|
||||
|
||||
static profile_storage_status_t profile_storage_append_index_entry(void)
|
||||
{
|
||||
char line_buffer[PROFILE_STORAGE_INDEX_LINE_BUFFER_SIZE];
|
||||
FIL index_file;
|
||||
UINT bytes_written = 0u;
|
||||
FRESULT result;
|
||||
int line_length;
|
||||
|
||||
line_length = snprintf(line_buffer,
|
||||
sizeof(line_buffer),
|
||||
"%s,%s,%s\r\n",
|
||||
g_profile_storage.display_name,
|
||||
g_profile_storage.profile_path,
|
||||
g_profile_storage.waveform_path);
|
||||
if ((line_length <= 0) || ((size_t)line_length >= sizeof(line_buffer)))
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_INDEX_UPDATE_ERROR);
|
||||
}
|
||||
|
||||
result = storage_sd_open_file(&index_file,
|
||||
APP_STORAGE_PROFILE_INDEX_FILE,
|
||||
FA_OPEN_ALWAYS | FA_WRITE);
|
||||
if (result != FR_OK)
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_INDEX_UPDATE_ERROR);
|
||||
}
|
||||
|
||||
result = f_lseek(&index_file, f_size(&index_file));
|
||||
if (result == FR_OK)
|
||||
{
|
||||
result = f_write(&index_file, line_buffer, (UINT)line_length, &bytes_written);
|
||||
}
|
||||
if (result == FR_OK)
|
||||
{
|
||||
result = f_sync(&index_file);
|
||||
}
|
||||
|
||||
(void)storage_sd_close_file(&index_file);
|
||||
|
||||
if ((result != FR_OK) || (bytes_written != (UINT)line_length))
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_INDEX_UPDATE_ERROR);
|
||||
}
|
||||
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_OK);
|
||||
}
|
||||
|
||||
void profile_storage_init(void)
|
||||
{
|
||||
profile_storage_reset_context();
|
||||
}
|
||||
|
||||
bool profile_storage_is_active(void)
|
||||
{
|
||||
return g_profile_storage.active;
|
||||
}
|
||||
|
||||
profile_storage_status_t profile_storage_get_last_status(void)
|
||||
{
|
||||
return g_profile_storage.last_status;
|
||||
}
|
||||
|
||||
profile_storage_status_t profile_storage_begin(const char *display_name,
|
||||
uint16_t profile_text_bytes,
|
||||
uint16_t waveform_text_bytes)
|
||||
{
|
||||
if (profile_storage_is_active())
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_SESSION_ACTIVE);
|
||||
}
|
||||
|
||||
profile_storage_reset_context();
|
||||
|
||||
if (!storage_sd_is_available())
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_STORAGE_UNAVAILABLE);
|
||||
}
|
||||
|
||||
if ((profile_text_bytes == 0u) || !profile_storage_copy_validated_name(g_profile_storage.display_name,
|
||||
sizeof(g_profile_storage.display_name),
|
||||
display_name))
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_NAME_INVALID);
|
||||
}
|
||||
|
||||
g_profile_storage.profile_expected_bytes = profile_text_bytes;
|
||||
g_profile_storage.waveform_expected_bytes = waveform_text_bytes;
|
||||
g_profile_storage.active = true;
|
||||
|
||||
if (profile_storage_find_free_paths(waveform_text_bytes) != PROFILE_STORAGE_STATUS_OK)
|
||||
{
|
||||
g_profile_storage.active = false;
|
||||
return g_profile_storage.last_status;
|
||||
}
|
||||
|
||||
if (profile_storage_open_target_files() != PROFILE_STORAGE_STATUS_OK)
|
||||
{
|
||||
g_profile_storage.active = false;
|
||||
profile_storage_clear_session_paths();
|
||||
return g_profile_storage.last_status;
|
||||
}
|
||||
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_OK);
|
||||
}
|
||||
|
||||
profile_storage_status_t profile_storage_write_chunk(uint16_t section_id,
|
||||
const uint8_t *data,
|
||||
uint16_t data_size)
|
||||
{
|
||||
if (!g_profile_storage.active)
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_NO_ACTIVE_SESSION);
|
||||
}
|
||||
|
||||
if ((data == NULL) || (data_size == 0u) || (data_size > APP_PROFILE_SAVE_MAX_DATA_BYTES_PER_PACKET))
|
||||
{
|
||||
profile_storage_abort_session();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_INVALID_ARGUMENT);
|
||||
}
|
||||
|
||||
if (section_id == APP_PROFILE_SAVE_SECTION_PROFILE_TEXT)
|
||||
{
|
||||
return profile_storage_write_to_file(&g_profile_storage.profile_file,
|
||||
&g_profile_storage.profile_received_bytes,
|
||||
g_profile_storage.profile_expected_bytes,
|
||||
data,
|
||||
data_size);
|
||||
}
|
||||
|
||||
if ((section_id == APP_PROFILE_SAVE_SECTION_WAVEFORM_TEXT) && g_profile_storage.waveform_file_open)
|
||||
{
|
||||
return profile_storage_write_to_file(&g_profile_storage.waveform_file,
|
||||
&g_profile_storage.waveform_received_bytes,
|
||||
g_profile_storage.waveform_expected_bytes,
|
||||
data,
|
||||
data_size);
|
||||
}
|
||||
|
||||
profile_storage_abort_session();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_SECTION_ERROR);
|
||||
}
|
||||
|
||||
profile_storage_status_t profile_storage_commit(void)
|
||||
{
|
||||
if (!g_profile_storage.active)
|
||||
{
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_NO_ACTIVE_SESSION);
|
||||
}
|
||||
|
||||
if ((g_profile_storage.profile_received_bytes != g_profile_storage.profile_expected_bytes) ||
|
||||
(g_profile_storage.waveform_received_bytes != g_profile_storage.waveform_expected_bytes))
|
||||
{
|
||||
profile_storage_abort_session();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_SIZE_MISMATCH);
|
||||
}
|
||||
|
||||
if (g_profile_storage.profile_file_open && (f_sync(&g_profile_storage.profile_file) != FR_OK))
|
||||
{
|
||||
profile_storage_abort_session();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_WRITE_ERROR);
|
||||
}
|
||||
|
||||
if (g_profile_storage.waveform_file_open && (f_sync(&g_profile_storage.waveform_file) != FR_OK))
|
||||
{
|
||||
profile_storage_abort_session();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_WRITE_ERROR);
|
||||
}
|
||||
|
||||
profile_storage_close_open_files();
|
||||
|
||||
if (profile_storage_append_index_entry() != PROFILE_STORAGE_STATUS_OK)
|
||||
{
|
||||
profile_storage_remove_partial_files();
|
||||
g_profile_storage.active = false;
|
||||
profile_storage_clear_session_paths();
|
||||
return g_profile_storage.last_status;
|
||||
}
|
||||
|
||||
g_profile_storage.active = false;
|
||||
g_profile_storage.profile_expected_bytes = 0u;
|
||||
g_profile_storage.waveform_expected_bytes = 0u;
|
||||
g_profile_storage.profile_received_bytes = 0u;
|
||||
g_profile_storage.waveform_received_bytes = 0u;
|
||||
profile_storage_clear_session_paths();
|
||||
return profile_storage_set_status(PROFILE_STORAGE_STATUS_OK);
|
||||
}
|
||||
|
||||
void profile_storage_cancel(void)
|
||||
{
|
||||
if (g_profile_storage.active)
|
||||
{
|
||||
profile_storage_abort_session();
|
||||
}
|
||||
|
||||
g_profile_storage.last_status = PROFILE_STORAGE_STATUS_OK;
|
||||
}
|
||||
Reference in New Issue
Block a user