/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include "settings_raw_iter.h" // Deleted records have their key stick around for at least DELETED_LIFETIME // before they can be garbage collected from the file in which they are // contained, that way they have time to propegate to all devices we end up // syncronizing with. For more information, refer to the sync protocol proposal: // https://pebbletechnology.atlassian.net/wiki/pages/viewpage.action?pageId=26837564 // // FIXME: See PBL-18945 #define DELETED_LIFETIME (0 * SECONDS_PER_DAY) //! A SettingsFile is just a simple binary key-value store. Keys can be strings, //! uint32_ts, or arbitrary bytes. Values are similarilly flexible. All //! operations are atomic, so a reboot in the middle of changing the value for a //! key will always either complete, returning the new value upon reboot, or //! will just return the old value. //! It also supports bidirectional syncronization between the phone & watch, //! using timestamps to resolve conflicts. //! Note that although all operations are atomic, they are not thread-safe. If //! you will be accessing a SettingsFile from multiple threads, make sure you //! use locks! // NOTE: These fields are internal, modify them at your own risk! typedef struct SettingsFile { SettingsRawIter iter; char *name; //! Maximum total space which can be used by this settings_file before a //! compaction will be forced. (Must be >= max_used_space) int max_space_total; //! Maximum space that can be used by valid records within this settings_file. //! Once this has been exceeded, attempting to add more keys or values will //! fail. int max_used_space; //! Amount of space in the settings_file that is currently dead, i.e. //! has been written to with some data, but that data is no longer valid. //! (overwritten records get added to this) int dead_space; //! Amount of space in the settings_file that is currently used by valid //! records. int used_space; //! When this file as a whole was last_modified. //! Defined as records.max(&:last_modified) uint32_t last_modified; //! The position of the current record in the iteration (if any). Necessary //! so that clients can read other records in the middle of iteration (i.e. //! settings_file_each()/settings_file_rewrite()), without messing up the //! state of the iteration. Set to 0 if not in use. int cur_record_pos; } SettingsFile; //! max_used_space should be >= 5317 for persist files to make sure we can //! always fit all of the records in the worst case (if the programmer stored //! nothing but booleans). //! Note: If the settings file already exists, the max_used_space parameter is //! ignored. We could change this if the need arises. status_t settings_file_open(SettingsFile *file, const char *name, int max_used_space); void settings_file_close(SettingsFile *file); bool settings_file_exists(SettingsFile *file, const void *key, size_t key_len); status_t settings_file_delete(SettingsFile *file, const void *key, size_t key_len); int settings_file_get_len(SettingsFile *file, const void *key, size_t key_len); //! val_out_len must exactly match the length of the record on disk. status_t settings_file_get(SettingsFile *file, const void *key, size_t key_len, void *val_out, size_t val_out_len); status_t settings_file_set(SettingsFile *file, const void *key, size_t key_len, const void *val, size_t val_len); //! Mark a record as synced. The flag will remain until the record is overwritten //! @param file the settings_file that contains the record //! @param key the key to the settings file. Note: keys can be up to 127 bytes //! @param key_len the length of the key status_t settings_file_mark_synced(SettingsFile *file, const void *key, size_t key_len); //! set a byte in a setting. This can only be used a byte at a time to guarantee //! atomicity. Do not use to modify several bytes in a row! //! Note that only the reset bits will be applied (it writes flash directly) status_t settings_file_set_byte( SettingsFile *file, const void *key, size_t key_len, size_t offset, uint8_t byte); ////////////////// // Each/rewrite // ////////////////// typedef void (*SettingsFileGetter)(SettingsFile *file, void *buf, size_t buf_len); typedef struct { uint32_t last_modified; SettingsFileGetter get_key; int key_len; SettingsFileGetter get_val; int val_len; bool dirty; // has the dirty flag set } SettingsRecordInfo; //! Callback used for using settings_file_each. //! The bool returned is used to control the iteration. //! - If a callback returns true, the iteration continues //! - If a callback returns false, the iteration stops. typedef bool (*SettingsFileEachCallback)(SettingsFile *file, SettingsRecordInfo *info, void *context); //! Calls cb for each and every entry within the given file. //! Note that you cannot modify the settings file while iterating. If you want //! to do this, try settings_file_rewrite instead. (you can read other entries //! without fault). status_t settings_file_each(SettingsFile *file, SettingsFileEachCallback cb, void *context); typedef void (*SettingsFileRewriteCallback)(SettingsFile *old_file, SettingsFile *new_file, SettingsRecordInfo *info, void *context); //! Opens a new SettingsFile with the same name as the original SettingsFile, //! in overwrite mode. This new file is passed into the given //! SettingsFileRewriteCallback, which is called for each entry within the //! original file. If you desire to preserve a key/value pair, you must write //! it to the new file. status_t settings_file_rewrite(SettingsFile *file, SettingsFileRewriteCallback cb, void *context); //! Callback used for using settings_file_rewrite_filtered. //! The bool returned is used to control whether or not the record is included in the file //! after compaction. This callback is not allowed to use any other settings_file calls. //! - If callback returns true, the record is included //! - If callback returns false, the record is not included typedef bool (*SettingsFileRewriteFilterCallback)(void *key, size_t key_len, void *value, size_t value_len, void *context); //! Opens a new SettingsFile with the same name as the original SettingsFile, //! in overwrite mode. Any records from the old file which pass through the filter_cb with //! a true result are included into the new file. This call is much faster than using //! settings_file_rewrite if all you are doing is excluding specific records from the old file. status_t settings_file_rewrite_filtered(SettingsFile *file, SettingsFileRewriteFilterCallback filter_cb, void *context);