diff --git a/applications/services/rpc/rpc_storage.c b/applications/services/rpc/rpc_storage.c index e28998e1..16e343fc 100644 --- a/applications/services/rpc/rpc_storage.c +++ b/applications/services/rpc/rpc_storage.c @@ -138,6 +138,41 @@ static void rpc_system_storage_info_process(const PB_Main* request, void* contex furi_record_close(RECORD_STORAGE); } +static void rpc_system_storage_timestamp_process(const PB_Main* request, void* context) { + furi_assert(request); + furi_assert(context); + furi_assert(request->which_content == PB_Main_storage_timestamp_request_tag); + + FURI_LOG_D(TAG, "Timestamp"); + + RpcStorageSystem* rpc_storage = context; + RpcSession* session = rpc_storage->session; + furi_assert(session); + + rpc_system_storage_reset_state(rpc_storage, session, true); + + PB_Main* response = malloc(sizeof(PB_Main)); + response->command_id = request->command_id; + + Storage* fs_api = furi_record_open(RECORD_STORAGE); + + const char* path = request->content.storage_timestamp_request.path; + uint32_t timestamp = 0; + FS_Error error = storage_common_timestamp(fs_api, path, ×tamp); + + response->command_status = rpc_system_storage_get_error(error); + response->which_content = PB_Main_empty_tag; + + if(error == FSE_OK) { + response->which_content = PB_Main_storage_timestamp_response_tag; + response->content.storage_timestamp_response.timestamp = timestamp; + } + + rpc_send_and_release(session, response); + free(response); + furi_record_close(RECORD_STORAGE); +} + static void rpc_system_storage_stat_process(const PB_Main* request, void* context) { furi_assert(request); furi_assert(context); @@ -672,6 +707,9 @@ void* rpc_system_storage_alloc(RpcSession* session) { rpc_handler.message_handler = rpc_system_storage_info_process; rpc_add_handler(session, PB_Main_storage_info_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_storage_timestamp_process; + rpc_add_handler(session, PB_Main_storage_timestamp_request_tag, &rpc_handler); + rpc_handler.message_handler = rpc_system_storage_stat_process; rpc_add_handler(session, PB_Main_storage_stat_request_tag, &rpc_handler); diff --git a/applications/services/storage/storage.c b/applications/services/storage/storage.c index 700408c9..1816bf92 100644 --- a/applications/services/storage/storage.c +++ b/applications/services/storage/storage.c @@ -39,6 +39,7 @@ Storage* storage_app_alloc() { for(uint8_t i = 0; i < STORAGE_COUNT; i++) { storage_data_init(&app->storage[i]); + storage_data_timestamp(&app->storage[i]); } #ifndef FURI_RAM_EXEC diff --git a/applications/services/storage/storage.h b/applications/services/storage/storage.h index 968b6904..9c133e9b 100644 --- a/applications/services/storage/storage.h +++ b/applications/services/storage/storage.h @@ -177,6 +177,16 @@ bool storage_dir_rewind(File* file); /******************* Common Functions *******************/ +/** Retrieves unix timestamp of last access + * + * @param storage The storage instance + * @param path path to file/directory + * @param timestamp the timestamp pointer + * + * @return FS_Error operation result + */ +FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp); + /** Retrieves information about a file/directory * @param app pointer to the api * @param path path to file/directory diff --git a/applications/services/storage/storage_cli.c b/applications/services/storage/storage_cli.c index 880fb970..0efcb5e2 100644 --- a/applications/services/storage/storage_cli.c +++ b/applications/services/storage/storage_cli.c @@ -32,6 +32,7 @@ static void storage_cli_print_usage() { printf("\tmkdir\t - creates a new directory\r\n"); printf("\tmd5\t - md5 hash of the file\r\n"); printf("\tstat\t - info about file or dir\r\n"); + printf("\ttimestamp\t - last modification timestamp\r\n"); }; static void storage_cli_print_error(FS_Error error) { @@ -386,6 +387,22 @@ static void storage_cli_stat(Cli* cli, FuriString* path) { furi_record_close(RECORD_STORAGE); } +static void storage_cli_timestamp(Cli* cli, FuriString* path) { + UNUSED(cli); + Storage* api = furi_record_open(RECORD_STORAGE); + + uint32_t timestamp = 0; + FS_Error error = storage_common_timestamp(api, furi_string_get_cstr(path), ×tamp); + + if(error != FSE_OK) { + printf("Invalid arguments\r\n"); + } else { + printf("Timestamp %lu\r\n", timestamp); + } + + furi_record_close(RECORD_STORAGE); +} + static void storage_cli_copy(Cli* cli, FuriString* old_path, FuriString* args) { UNUSED(cli); Storage* api = furi_record_open(RECORD_STORAGE); @@ -578,6 +595,11 @@ void storage_cli(Cli* cli, FuriString* args, void* context) { break; } + if(furi_string_cmp_str(cmd, "timestamp") == 0) { + storage_cli_timestamp(cli, path); + break; + } + storage_cli_print_usage(); } while(false); diff --git a/applications/services/storage/storage_external_api.c b/applications/services/storage/storage_external_api.c index c0c730fb..6854ef7f 100644 --- a/applications/services/storage/storage_external_api.c +++ b/applications/services/storage/storage_external_api.c @@ -354,6 +354,16 @@ bool storage_dir_rewind(File* file) { /****************** COMMON ******************/ +FS_Error storage_common_timestamp(Storage* storage, const char* path, uint32_t* timestamp) { + S_API_PROLOGUE; + + SAData data = {.ctimestamp = {.path = path, .timestamp = timestamp}}; + + S_API_MESSAGE(StorageCommandCommonTimestamp); + S_API_EPILOGUE; + return S_RETURN_ERROR; +} + FS_Error storage_common_stat(Storage* storage, const char* path, FileInfo* fileinfo) { S_API_PROLOGUE; diff --git a/applications/services/storage/storage_glue.c b/applications/services/storage/storage_glue.c index c5682f67..c6ff08bd 100644 --- a/applications/services/storage/storage_glue.c +++ b/applications/services/storage/storage_glue.c @@ -82,6 +82,14 @@ const char* storage_data_status_text(StorageData* storage) { return result; } +void storage_data_timestamp(StorageData* storage) { + storage->timestamp = furi_hal_rtc_get_timestamp(); +} + +uint32_t storage_data_get_timestamp(StorageData* storage) { + return storage->timestamp; +} + /****************** storage glue ******************/ bool storage_has_file(const File* file, StorageData* storage_data) { diff --git a/applications/services/storage/storage_glue.h b/applications/services/storage/storage_glue.h index 53fa0de1..6fdc7009 100644 --- a/applications/services/storage/storage_glue.h +++ b/applications/services/storage/storage_glue.h @@ -42,6 +42,8 @@ bool storage_data_lock(StorageData* storage); bool storage_data_unlock(StorageData* storage); StorageStatus storage_data_status(StorageData* storage); const char* storage_data_status_text(StorageData* storage); +void storage_data_timestamp(StorageData* storage); +uint32_t storage_data_get_timestamp(StorageData* storage); LIST_DEF( StorageFileList, @@ -58,6 +60,7 @@ struct StorageData { FuriMutex* mutex; StorageStatus status; StorageFileList_t files; + uint32_t timestamp; }; bool storage_has_file(const File* file, StorageData* storage_data); diff --git a/applications/services/storage/storage_i.h b/applications/services/storage/storage_i.h index 5c836ccd..406fc921 100644 --- a/applications/services/storage/storage_i.h +++ b/applications/services/storage/storage_i.h @@ -1,5 +1,6 @@ #pragma once #include +#include #include #include "storage_glue.h" #include "storage_sd_api.h" diff --git a/applications/services/storage/storage_message.h b/applications/services/storage/storage_message.h index 78cd1e03..98726801 100644 --- a/applications/services/storage/storage_message.h +++ b/applications/services/storage/storage_message.h @@ -42,6 +42,11 @@ typedef struct { uint16_t name_length; } SADataDRead; +typedef struct { + const char* path; + uint32_t* timestamp; +} SADataCTimestamp; + typedef struct { const char* path; FileInfo* fileinfo; @@ -78,6 +83,7 @@ typedef union { SADataDOpen dopen; SADataDRead dread; + SADataCTimestamp ctimestamp; SADataCStat cstat; SADataCFSInfo cfsinfo; @@ -112,6 +118,7 @@ typedef enum { StorageCommandDirClose, StorageCommandDirRead, StorageCommandDirRewind, + StorageCommandCommonTimestamp, StorageCommandCommonStat, StorageCommandCommonRemove, StorageCommandCommonMkDir, diff --git a/applications/services/storage/storage_processing.c b/applications/services/storage/storage_processing.c index 8643e974..795a5d11 100644 --- a/applications/services/storage/storage_processing.c +++ b/applications/services/storage/storage_processing.c @@ -114,6 +114,9 @@ bool storage_process_file_open( if(storage_path_already_open(real_path, storage->files)) { file->error_id = FSE_ALREADY_OPEN; } else { + if(access_mode & FSAM_WRITE) { + storage_data_timestamp(storage); + } storage_push_storage_file(file, real_path, type, storage); FS_CALL(storage, file.open(storage, file, remove_vfs(path), access_mode, open_mode)); } @@ -166,6 +169,7 @@ static uint16_t storage_process_file_write( if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { + storage_data_timestamp(storage); FS_CALL(storage, file.write(storage, file, buff, bytes_to_write)); } @@ -209,6 +213,7 @@ static bool storage_process_file_truncate(Storage* app, File* file) { if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { + storage_data_timestamp(storage); FS_CALL(storage, file.truncate(storage, file)); } @@ -222,6 +227,7 @@ static bool storage_process_file_sync(Storage* app, File* file) { if(storage == NULL) { file->error_id = FSE_INVALID_PARAMETER; } else { + storage_data_timestamp(storage); FS_CALL(storage, file.sync(storage, file)); } @@ -332,6 +338,21 @@ bool storage_process_dir_rewind(Storage* app, File* file) { /******************* Common FS Functions *******************/ +static FS_Error + storage_process_common_timestamp(Storage* app, const char* path, uint32_t* timestamp) { + FS_Error ret = FSE_OK; + StorageType type = storage_get_type_by_path(app, path); + + if(storage_type_is_not_valid(type)) { + ret = FSE_INVALID_NAME; + } else { + StorageData* storage = storage_get_storage_by_type(app, type); + *timestamp = storage_data_get_timestamp(storage); + } + + return ret; +} + static FS_Error storage_process_common_stat(Storage* app, const char* path, FileInfo* fileinfo) { FS_Error ret = FSE_OK; StorageType type = storage_get_type_by_path(app, path); @@ -366,6 +387,7 @@ static FS_Error storage_process_common_remove(Storage* app, const char* path) { break; } + storage_data_timestamp(storage); FS_CALL(storage, common.remove(storage, remove_vfs(path))); } while(false); @@ -382,6 +404,7 @@ static FS_Error storage_process_common_mkdir(Storage* app, const char* path) { ret = FSE_INVALID_NAME; } else { StorageData* storage = storage_get_storage_by_type(app, type); + storage_data_timestamp(storage); FS_CALL(storage, common.mkdir(storage, remove_vfs(path))); } @@ -417,6 +440,7 @@ static FS_Error storage_process_sd_format(Storage* app) { ret = FSE_NOT_READY; } else { ret = sd_format_card(&app->storage[ST_EXT]); + storage_data_timestamp(&app->storage[ST_EXT]); } return ret; @@ -429,6 +453,7 @@ static FS_Error storage_process_sd_unmount(Storage* app) { ret = FSE_NOT_READY; } else { sd_unmount_card(&app->storage[ST_EXT]); + storage_data_timestamp(&app->storage[ST_EXT]); } return ret; @@ -541,6 +566,10 @@ void storage_process_message_internal(Storage* app, StorageMessage* message) { message->return_data->bool_value = storage_process_dir_rewind(app, message->data->file.file); break; + case StorageCommandCommonTimestamp: + message->return_data->error_value = storage_process_common_timestamp( + app, message->data->ctimestamp.path, message->data->ctimestamp.timestamp); + break; case StorageCommandCommonStat: message->return_data->error_value = storage_process_common_stat( app, message->data->cstat.path, message->data->cstat.fileinfo); diff --git a/applications/services/storage/storages/storage_ext.c b/applications/services/storage/storages/storage_ext.c index 7341a6ec..0c81a000 100644 --- a/applications/services/storage/storages/storage_ext.c +++ b/applications/services/storage/storages/storage_ext.c @@ -90,6 +90,7 @@ static bool sd_mount_card(StorageData* storage, bool notify) { } } + storage_data_timestamp(storage); storage_data_unlock(storage); return result; diff --git a/assets/protobuf b/assets/protobuf index 6727eaf2..e5af96e0 160000 --- a/assets/protobuf +++ b/assets/protobuf @@ -1 +1 @@ -Subproject commit 6727eaf287db077dcd28719cd764f5804712223e +Subproject commit e5af96e08fea8351898f7b8c6d1e34ce5fd6cdef diff --git a/firmware/targets/f7/api_symbols.csv b/firmware/targets/f7/api_symbols.csv index 0d9a5b56..65e6f485 100644 --- a/firmware/targets/f7/api_symbols.csv +++ b/firmware/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,7.2,, +Version,+,7.3,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -1251,6 +1251,7 @@ Function,+,furi_hal_rtc_get_fault_data,uint32_t, Function,+,furi_hal_rtc_get_log_level,uint8_t, Function,+,furi_hal_rtc_get_pin_fails,uint32_t, Function,+,furi_hal_rtc_get_register,uint32_t,FuriHalRtcRegister +Function,+,furi_hal_rtc_get_timestamp,uint32_t, Function,-,furi_hal_rtc_init,void, Function,-,furi_hal_rtc_init_early,void, Function,+,furi_hal_rtc_is_flag_set,_Bool,FuriHalRtcFlag @@ -2235,6 +2236,7 @@ Function,+,storage_common_mkdir,FS_Error,"Storage*, const char*" Function,+,storage_common_remove,FS_Error,"Storage*, const char*" Function,+,storage_common_rename,FS_Error,"Storage*, const char*, const char*" Function,+,storage_common_stat,FS_Error,"Storage*, const char*, FileInfo*" +Function,+,storage_common_timestamp,FS_Error,"Storage*, const char*, uint32_t*" Function,+,storage_dir_close,_Bool,File* Function,+,storage_dir_open,_Bool,"File*, const char*" Function,+,storage_dir_read,_Bool,"File*, FileInfo*, char*, uint16_t" diff --git a/firmware/targets/f7/furi_hal/furi_hal_rtc.c b/firmware/targets/f7/furi_hal/furi_hal_rtc.c index 24dad38f..14ece946 100644 --- a/firmware/targets/f7/furi_hal/furi_hal_rtc.c +++ b/firmware/targets/f7/furi_hal/furi_hal_rtc.c @@ -318,6 +318,12 @@ uint32_t furi_hal_rtc_get_pin_fails() { return furi_hal_rtc_get_register(FuriHalRtcRegisterPinFails); } +uint32_t furi_hal_rtc_get_timestamp() { + FuriHalRtcDateTime datetime = {0}; + furi_hal_rtc_get_datetime(&datetime); + return furi_hal_rtc_datetime_to_timestamp(&datetime); +} + uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime) { uint32_t timestamp = 0; uint8_t years = 0; diff --git a/firmware/targets/furi_hal_include/furi_hal_rtc.h b/firmware/targets/furi_hal_include/furi_hal_rtc.h index bdae3b93..361225fb 100644 --- a/firmware/targets/furi_hal_include/furi_hal_rtc.h +++ b/firmware/targets/furi_hal_include/furi_hal_rtc.h @@ -93,6 +93,8 @@ void furi_hal_rtc_set_pin_fails(uint32_t value); uint32_t furi_hal_rtc_get_pin_fails(); +uint32_t furi_hal_rtc_get_timestamp(); + uint32_t furi_hal_rtc_datetime_to_timestamp(FuriHalRtcDateTime* datetime); #ifdef __cplusplus