diff --git a/firmware/application/apps/acars_app.cpp b/firmware/application/apps/acars_app.cpp index b22a9632d..39302b1e1 100644 --- a/firmware/application/apps/acars_app.cpp +++ b/firmware/application/apps/acars_app.cpp @@ -98,10 +98,9 @@ ACARSAppView::ACARSAppView(NavigationView& nav) { logging = v; }; - make_new_directory(LOG_ROOT_DIR); logger = std::make_unique(); if (logger) - logger->append( LOG_ROOT_DIR "/ACARS.TXT" ); + logger->append(LOG_ROOT_DIR "/ACARS.TXT"); } ACARSAppView::~ACARSAppView() { diff --git a/firmware/application/apps/ais_app.cpp b/firmware/application/apps/ais_app.cpp index 291794b44..49fac752b 100644 --- a/firmware/application/apps/ais_app.cpp +++ b/firmware/application/apps/ais_app.cpp @@ -397,7 +397,6 @@ AISAppView::AISAppView(NavigationView& nav) : nav_ { nav } { this->on_show_list(); }; - make_new_directory(LOG_ROOT_DIR); logger = std::make_unique(); if( logger ) { logger->append( LOG_ROOT_DIR "/AIS.TXT" ); diff --git a/firmware/application/apps/analog_audio_app.hpp b/firmware/application/apps/analog_audio_app.hpp index f203d5d68..2be7b8c97 100644 --- a/firmware/application/apps/analog_audio_app.hpp +++ b/firmware/application/apps/analog_audio_app.hpp @@ -232,7 +232,7 @@ class AnalogAudioView : public View { RecordView record_view { { 0 * 8, 2 * 16, 30 * 8, 1 * 16 }, - u"AUD", + u"AUD", u"AUDIO", RecordView::FileType::WAV, 4096, 4 diff --git a/firmware/application/apps/capture_app.hpp b/firmware/application/apps/capture_app.hpp index c23c3b74b..ab6a1c9ea 100644 --- a/firmware/application/apps/capture_app.hpp +++ b/firmware/application/apps/capture_app.hpp @@ -108,7 +108,7 @@ class CaptureAppView : public View { RecordView record_view { { 0 * 8, 2 * 16, 30 * 8, 1 * 16 }, - u"BBD_????", RecordView::FileType::RawS16, 16384, 3 + u"BBD_????.*", u"CAPTURES", RecordView::FileType::RawS16, 16384, 3 }; spectrum::WaterfallWidget waterfall { }; diff --git a/firmware/application/apps/ert_app.cpp b/firmware/application/apps/ert_app.cpp index 1260c3210..b626ac5d1 100644 --- a/firmware/application/apps/ert_app.cpp +++ b/firmware/application/apps/ert_app.cpp @@ -130,7 +130,6 @@ ERTAppView::ERTAppView(NavigationView&) { static_cast(receiver_model.vga()), }); */ - make_new_directory(LOG_ROOT_DIR); logger = std::make_unique(); if( logger ) { logger->append( LOG_ROOT_DIR "/ERT.TXT" ); diff --git a/firmware/application/apps/pocsag_app.cpp b/firmware/application/apps/pocsag_app.cpp index 91d599d10..15aeca158 100644 --- a/firmware/application/apps/pocsag_app.cpp +++ b/firmware/application/apps/pocsag_app.cpp @@ -129,10 +129,9 @@ POCSAGAppView::POCSAGAppView(NavigationView& nav) { ignore_address /= 10; } - make_new_directory(LOG_ROOT_DIR); logger = std::make_unique(); if (logger) - logger->append( LOG_ROOT_DIR "/POCSAG.TXT" ); + logger->append(LOG_ROOT_DIR "/POCSAG.TXT"); audio::output::start(); audio::output::unmute(); diff --git a/firmware/application/apps/tpms_app.cpp b/firmware/application/apps/tpms_app.cpp index 2e2005903..78d929450 100644 --- a/firmware/application/apps/tpms_app.cpp +++ b/firmware/application/apps/tpms_app.cpp @@ -194,7 +194,6 @@ TPMSAppView::TPMSAppView(NavigationView&) { options_temperature.set_selected_index(0, true); - make_new_directory(LOG_ROOT_DIR); logger = std::make_unique(); if( logger ) { logger->append( LOG_ROOT_DIR "/TPMS.TXT" ); diff --git a/firmware/application/apps/ui_adsb_rx.cpp b/firmware/application/apps/ui_adsb_rx.cpp index c924cda19..7b1c6bbfa 100644 --- a/firmware/application/apps/ui_adsb_rx.cpp +++ b/firmware/application/apps/ui_adsb_rx.cpp @@ -117,8 +117,6 @@ ADSBRxAircraftDetailsView::ADSBRxAircraftDetailsView( std::unique_ptr logger { }; - make_new_directory(LOG_ROOT_DIR); - icao_code = to_string_hex(entry_copy.ICAO_address, 6); text_icao_address.set(to_string_hex(entry_copy.ICAO_address, 6)); diff --git a/firmware/application/apps/ui_afsk_rx.cpp b/firmware/application/apps/ui_afsk_rx.cpp index 2fcdefe03..53587efef 100644 --- a/firmware/application/apps/ui_afsk_rx.cpp +++ b/firmware/application/apps/ui_afsk_rx.cpp @@ -107,10 +107,9 @@ AFSKRxView::AFSKRxView(NavigationView& nav) { nav.push(); }; - make_new_directory(LOG_ROOT_DIR); logger = std::make_unique(); if (logger) - logger->append( LOG_ROOT_DIR "/AFSK.TXT" ); + logger->append(LOG_ROOT_DIR "/AFSK.TXT"); // Auto-configure modem for LCR RX (will be removed later) baseband::set_afsk(persistent_memory::modem_baudrate(), 8, 0, false); diff --git a/firmware/application/apps/ui_aprs_rx.cpp b/firmware/application/apps/ui_aprs_rx.cpp index 3b7ab519d..8908238cb 100644 --- a/firmware/application/apps/ui_aprs_rx.cpp +++ b/firmware/application/apps/ui_aprs_rx.cpp @@ -141,10 +141,9 @@ APRSRxView::APRSRxView(NavigationView& nav, Rect parent_rect) : View(parent_rect options_region.set_selected_index(0, true); - make_new_directory(LOG_ROOT_DIR); logger = std::make_unique(); if (logger) - logger->append( LOG_ROOT_DIR "/APRS.TXT" ); + logger->append(LOG_ROOT_DIR "/APRS.TXT"); baseband::set_aprs(1200); diff --git a/firmware/application/apps/ui_aprs_rx.hpp b/firmware/application/apps/ui_aprs_rx.hpp index 1a37df92b..06030ad21 100644 --- a/firmware/application/apps/ui_aprs_rx.hpp +++ b/firmware/application/apps/ui_aprs_rx.hpp @@ -235,7 +235,7 @@ class APRSRxView : public View { // DEBUG RecordView record_view { { 0 * 8, 1 * 16, 30 * 8, 1 * 16 }, - u"AFS_????", RecordView::FileType::WAV, 4096, 4 + u"AFS_????.WAV", u"APRS", RecordView::FileType::WAV, 4096, 4 }; Console console { diff --git a/firmware/application/apps/ui_fileman.cpp b/firmware/application/apps/ui_fileman.cpp index cc837d5e1..dc7e6a40e 100644 --- a/firmware/application/apps/ui_fileman.cpp +++ b/firmware/application/apps/ui_fileman.cpp @@ -42,8 +42,7 @@ bool is_hidden_file(const fs::path& path) { // Gets a truncated name from a path for display. std::string truncate(const fs::path& path, size_t max_length) { - auto name = path.string(); - return name.length() <= max_length ? name : name.substr(0, max_length); + return ::truncate(path.string(), max_length); } // Gets a human readable file size string. diff --git a/firmware/application/apps/ui_sonde.cpp b/firmware/application/apps/ui_sonde.cpp index 45532251d..bdbe1a5c7 100644 --- a/firmware/application/apps/ui_sonde.cpp +++ b/firmware/application/apps/ui_sonde.cpp @@ -133,7 +133,6 @@ SondeView::SondeView(NavigationView& nav) { 999); //set a dummy heading out of range to draw a cross...probably not ideal? }; - make_new_directory(LOG_ROOT_DIR); logger = std::make_unique(); if (logger) logger->append( LOG_ROOT_DIR "/SONDE.TXT" ); diff --git a/firmware/application/apps/ui_test.cpp b/firmware/application/apps/ui_test.cpp index e42200aa1..a5e816159 100644 --- a/firmware/application/apps/ui_test.cpp +++ b/firmware/application/apps/ui_test.cpp @@ -79,7 +79,6 @@ TestView::TestView(NavigationView& nav) { cal_value = raw_alt - 0x80; }; - logger = std::make_unique(); if (logger) logger->append("saucepan.txt"); diff --git a/firmware/application/file.cpp b/firmware/application/file.cpp index 652d7627e..4a63e9761 100644 --- a/firmware/application/file.cpp +++ b/firmware/application/file.cpp @@ -124,50 +124,85 @@ Optional File::sync() { } } -static std::filesystem::path find_last_file_matching_pattern(const std::filesystem::path& pattern) { - std::filesystem::path last_match; - for(const auto& entry : std::filesystem::directory_iterator(u"", pattern)) { - if( std::filesystem::is_regular_file(entry.status()) ) { +/* Range used for filename matching. + * Start and end are inclusive positions of "???" */ +struct pattern_range { + size_t start; + size_t end; +}; + +/* Finds the last file matching the specified pattern that + * can be automatically incremented (digits in pattern). + * NB: assumes a patten with contiguous '?' like "FOO_???.txt". */ +static std::filesystem::path find_last_ordinal_match( + const std::filesystem::path& folder, + const std::filesystem::path& pattern, + pattern_range range +) { + auto last_match = std::filesystem::path(); + auto can_increment = [range](const auto& path) { + for (auto i = range.start; i <= range.end; ++i) + if (!isdigit(path.native()[i])) + return false; + + return true; + }; + + for (const auto& entry : std::filesystem::directory_iterator(folder, pattern)) { + if (std::filesystem::is_regular_file(entry.status()) && can_increment(entry.path())) { const auto& match = entry.path(); if( match > last_match ) { last_match = match; } } } + return last_match; } -static std::filesystem::path increment_filename_stem_ordinal(std::filesystem::path path) { - auto t = path.replace_extension().native(); - auto it = t.rbegin(); +/* Given a file path like "FOO_0001.txt" increment it to "FOO_0002.txt". */ +static std::filesystem::path increment_filename_ordinal( + const std::filesystem::path& path, pattern_range range +) { + auto name = path.filename().native(); - // Increment decimal number before the extension. - for(; it != t.rend(); ++it) { - const auto c = *it; - if( c < '0' ) { + for (auto i = range.end; i >= range.start; --i) { + auto& c = name[i]; + + // Not a digit or would overflow the counter. + if (c < u'0' || c > u'9' || (c == u'9' && i == range.start)) return { }; - } else if( c < '9' ) { - *it += 1; + + if (c == u'9') + c = '0'; + else { + c++; break; - } else if( c == '9' ) { - *it = '0'; - } else { - return { }; } } - return t; + return { name }; } -std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_pattern) { - const auto next_filename = find_last_file_matching_pattern(filename_pattern.replace_extension(u".*")); - if( next_filename.empty() ) { - auto pattern_s = filename_pattern.replace_extension().native(); - std::replace(std::begin(pattern_s), std::end(pattern_s), '?', '0'); - return pattern_s; - } else { - return increment_filename_stem_ordinal(next_filename); +std::filesystem::path next_filename_matching_pattern(const std::filesystem::path& filename_pattern) { + auto path = filename_pattern.parent_path(); + auto pattern = filename_pattern.filename(); + auto range = pattern_range { + pattern.native().find_first_of(u'?'), + pattern.native().find_last_of(u'?') + }; + + const auto match = find_last_ordinal_match(path, pattern, range); + + if (match.empty()) { + auto pattern_str = pattern.native(); + for (auto i = range.start; i <= range.end; ++i) + pattern_str[i] = u'0'; + return path / pattern_str; } + + auto next_name = increment_filename_ordinal(match, range); + return next_name.empty() ? next_name : path / next_name; } std::vector scan_root_files(const std::filesystem::path& directory, @@ -214,7 +249,7 @@ std::filesystem::filesystem_error copy_file( ) { File src; File dst; - constexpr size_t buffer_size = 512; + constexpr size_t buffer_size = 128; uint8_t buffer[buffer_size]; auto error = src.open(file_path); @@ -261,6 +296,19 @@ std::filesystem::filesystem_error make_new_directory( return { f_mkdir(reinterpret_cast(dir_path.c_str())) }; } +std::filesystem::filesystem_error ensure_directory( + const std::filesystem::path& dir_path +) { + if (dir_path.empty() || std::filesystem::file_exists(dir_path)) + return { }; + + auto result = ensure_directory(dir_path.parent_path()); + if (result.code()) + return result; + + return make_new_directory(dir_path); +} + namespace std { namespace filesystem { @@ -383,8 +431,10 @@ directory_iterator::directory_iterator( ) : pattern { wild } { impl = std::make_shared(); - const auto result = f_findfirst(&impl->dir, &impl->filinfo, reinterpret_cast(path.c_str()), reinterpret_cast(pattern.c_str())); - if( result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') { + const auto result = f_findfirst(&impl->dir, &impl->filinfo, + reinterpret_cast(path.c_str()), + reinterpret_cast(pattern.c_str())); + if (result != FR_OK || impl->filinfo.fname[0] == (TCHAR)'\0') { impl.reset(); // TODO: Throw exception if/when I enable exceptions... } diff --git a/firmware/application/file.hpp b/firmware/application/file.hpp index 6b3a9df1d..342d8d990 100644 --- a/firmware/application/file.hpp +++ b/firmware/application/file.hpp @@ -59,6 +59,10 @@ struct filesystem_error { std::string what() const; + bool ok() const { + return err == FR_OK; + } + private: uint32_t err { FR_OK }; }; @@ -258,12 +262,17 @@ std::filesystem::filesystem_error copy_file(const std::filesystem::path& file_pa FATTimestamp file_created_date(const std::filesystem::path& file_path); std::filesystem::filesystem_error make_new_file(const std::filesystem::path& file_path); std::filesystem::filesystem_error make_new_directory(const std::filesystem::path& dir_path); +std::filesystem::filesystem_error ensure_directory(const std::filesystem::path& dir_path); std::vector scan_root_files(const std::filesystem::path& directory, const std::filesystem::path& extension); std::vector scan_root_directories(const std::filesystem::path& directory); -/* Gets an auto incrementing filename. */ -std::filesystem::path next_filename_stem_matching_pattern(std::filesystem::path filename_stem_pattern); +/* Gets an auto incrementing filename stem. + * Pattern should be like "FOO_???.txt" where ??? will be replaced by digits. + * Pattern may also contain a folder path like "LOGS/FOO_???.txt". + * Pattern '?' must be contiguous (bad: "FOO?_??") + * Returns empty path if a filename could not be created. */ +std::filesystem::path next_filename_matching_pattern(const std::filesystem::path& pattern); /* Values added to FatFs FRESULT enum, values outside the FRESULT data type */ static_assert(sizeof(FIL::err) == 1, "FatFs FIL::err size not expected."); diff --git a/firmware/application/log_file.hpp b/firmware/application/log_file.hpp index 37c2a1eae..4d5fd4520 100644 --- a/firmware/application/log_file.hpp +++ b/firmware/application/log_file.hpp @@ -35,6 +35,10 @@ using namespace lpc43xx; class LogFile { public: Optional append(const std::filesystem::path& filename) { + auto result = ensure_directory(filename.parent_path()); + if (result.code()) + return { result }; + return file.append(filename); } diff --git a/firmware/application/string_format.cpp b/firmware/application/string_format.cpp index 0af9dc3d2..9da9a23d9 100644 --- a/firmware/application/string_format.cpp +++ b/firmware/application/string_format.cpp @@ -256,8 +256,17 @@ double get_decimals(double num, int16_t mult, bool round) { return intnum; } -std::string trimr(std::string str) -{ +std::string trim(const std::string& str) { + auto first = str.find_first_not_of(' '); + auto last = str.find_last_not_of(' '); + return str.substr(first, last - first); +} + +std::string trimr(std::string str) { size_t last = str.find_last_not_of(' '); return (last!=std::string::npos) ? str.substr(0, last+1) : ""; // Remove the trailing spaces +} + +std::string truncate(const std::string& str, size_t length) { + return str.length() <= length ? str : str.substr(0, length); } \ No newline at end of file diff --git a/firmware/application/string_format.hpp b/firmware/application/string_format.hpp index 848a53e3e..e74cb9b8a 100644 --- a/firmware/application/string_format.hpp +++ b/firmware/application/string_format.hpp @@ -59,6 +59,8 @@ std::string to_string_FAT_timestamp(const FATTimestamp& timestamp); std::string unit_auto_scale(double n, const uint32_t base_nano, uint32_t precision); double get_decimals(double num, int16_t mult, bool round = false); //euquiq added +std::string trim(const std::string& str); // Remove whitespace at ends. std::string trimr(std::string str); // Remove trailing spaces +std::string truncate(const std::string& str, size_t length); #endif/*__STRING_FORMAT_H__*/ diff --git a/firmware/application/ui_navigation.cpp b/firmware/application/ui_navigation.cpp index 1870b126f..7df55e688 100644 --- a/firmware/application/ui_navigation.cpp +++ b/firmware/application/ui_navigation.cpp @@ -367,13 +367,15 @@ void SystemStatusView::on_bias_tee() { }*/ void SystemStatusView::on_camera() { - auto path = next_filename_stem_matching_pattern(u"SCR_????"); + ensure_directory("SCREENSHOTS"); + auto path = next_filename_matching_pattern(u"SCREENSHOTS/SCR_????.PNG"); + if( path.empty() ) { return; } PNGWriter png; - auto create_error = png.create(path.replace_extension(u".PNG")); + auto create_error = png.create(path); if( create_error.is_valid() ) { return; } diff --git a/firmware/application/ui_record_view.cpp b/firmware/application/ui_record_view.cpp index 687c29640..1a3c3df39 100644 --- a/firmware/application/ui_record_view.cpp +++ b/firmware/application/ui_record_view.cpp @@ -55,16 +55,19 @@ namespace ui { RecordView::RecordView( const Rect parent_rect, - std::filesystem::path filename_stem_pattern, + const std::filesystem::path& filename_stem_pattern, + const std::filesystem::path& folder, const FileType file_type, const size_t write_size, const size_t buffer_count ) : View { parent_rect }, filename_stem_pattern { filename_stem_pattern }, + folder { folder }, file_type { file_type }, write_size { write_size }, buffer_count { buffer_count } { + ensure_directory(folder); add_children({ &rect_background, //&button_pitch_rssi, @@ -154,7 +157,6 @@ void RecordView::start() { return; } - std::filesystem::path base_path; if(filename_date_frequency) { rtcGetTime(&RTCD1, &datetime); @@ -167,9 +169,11 @@ void RecordView::start() { to_string_dec_uint(datetime.minute()) + to_string_dec_uint(datetime.second()); - base_path = filename_stem_pattern.string() + "_" + date_time + "_" + to_string_freq(receiver_model.tuning_frequency()) + "Hz"; + base_path = filename_stem_pattern.string() + "_" + date_time + "_" + + trim(to_string_freq(receiver_model.tuning_frequency())) + "Hz"; + base_path = folder / base_path; } else { - base_path = next_filename_stem_matching_pattern(filename_stem_pattern); + base_path = next_filename_matching_pattern(folder / filename_stem_pattern); } if( base_path.empty() ) { @@ -217,7 +221,7 @@ void RecordView::start() { }; if( writer ) { - text_record_filename.set(base_path.replace_extension().string()); + text_record_filename.set(truncate(base_path.filename().string(), 8)); button_record.set_bitmap(&bitmap_stop); capture_thread = std::make_unique( std::move(writer), @@ -275,7 +279,7 @@ void RecordView::on_tick_second() { void RecordView::update_status_display() { if( is_active() ) { const auto dropped_percent = std::min(99U, capture_thread->state().dropped_percent()); - const auto s = to_string_dec_uint(dropped_percent, 2, ' ') + "\%"; + const auto s = to_string_dec_uint(dropped_percent, 2, ' ') + "%"; text_record_dropped.set(s); } diff --git a/firmware/application/ui_record_view.hpp b/firmware/application/ui_record_view.hpp index 097e52cb8..241e8d559 100644 --- a/firmware/application/ui_record_view.hpp +++ b/firmware/application/ui_record_view.hpp @@ -46,7 +46,8 @@ class RecordView : public View { RecordView( const Rect parent_rect, - std::filesystem::path filename_stem_pattern, + const std::filesystem::path& filename_stem_pattern, + const std::filesystem::path& folder, FileType file_type, const size_t write_size, const size_t buffer_count @@ -83,6 +84,7 @@ class RecordView : public View { rtc::RTC datetime { }; const std::filesystem::path filename_stem_pattern; + const std::filesystem::path folder; const FileType file_type; const size_t write_size; const size_t buffer_count;