mirror of
https://github.com/google/pebble.git
synced 2026-02-19 03:36:51 -05:00
285 lines
9.3 KiB
C
285 lines
9.3 KiB
C
/*
|
|
* 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.
|
|
*/
|
|
|
|
#include "reminder_db.h"
|
|
#include "sync.h"
|
|
#include "sync_util.h"
|
|
#include "timeline_item_storage.h"
|
|
|
|
#include "util/uuid.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "services/common/analytics/analytics.h"
|
|
#include "services/normal/timeline/reminders.h"
|
|
#include "system/passert.h"
|
|
#include "system/logging.h"
|
|
#include "util/units.h"
|
|
|
|
#define REMINDER_DB_FILE_NAME "reminderdb"
|
|
#define REMINDER_DB_MAX_SIZE KiBYTES(40)
|
|
#define MAX_REMINDER_SIZE SETTINGS_VAL_MAX_LEN
|
|
#define MAX_REMINDER_AGE (15 * SECONDS_PER_MINUTE)
|
|
|
|
typedef struct {
|
|
TimelineItemStorageFilterCallback filter_cb;
|
|
time_t timestamp;
|
|
const char *title;
|
|
TimelineItem *reminder_out;
|
|
bool match;
|
|
} ReminderInfo;
|
|
|
|
static TimelineItemStorage s_storage;
|
|
|
|
static status_t prv_read_item_header(TimelineItem *item_out, TimelineItemId *id) {
|
|
SerializedTimelineItemHeader hdr = {{{0}}};
|
|
status_t rv = reminder_db_read((uint8_t *)id, sizeof(TimelineItemId), (uint8_t *)&hdr,
|
|
sizeof(SerializedTimelineItemHeader));
|
|
timeline_item_deserialize_header(item_out, &hdr);
|
|
return rv;
|
|
}
|
|
|
|
/////////////////////////
|
|
// Reminder DB specific API
|
|
/////////////////////////
|
|
|
|
status_t reminder_db_delete_with_parent(const TimelineItemId *parent_id) {
|
|
return (timeline_item_storage_delete_with_parent(&s_storage, parent_id,
|
|
reminders_handle_reminder_removed));
|
|
}
|
|
|
|
status_t reminder_db_read_item(TimelineItem *item_out, TimelineItemId *id) {
|
|
size_t size = reminder_db_get_len((uint8_t *)id, sizeof(TimelineItemId));
|
|
if (size == 0) {
|
|
return E_DOES_NOT_EXIST;
|
|
}
|
|
uint8_t *read_buf = kernel_malloc_check(size);
|
|
status_t rv = reminder_db_read((uint8_t *)id, sizeof(TimelineItemId), read_buf, size);
|
|
if (rv != S_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
|
|
SerializedTimelineItemHeader *header = (SerializedTimelineItemHeader *)read_buf;
|
|
uint8_t *payload = read_buf + sizeof(SerializedTimelineItemHeader);
|
|
if (!timeline_item_deserialize_item(item_out, header, payload)) {
|
|
rv = E_INTERNAL;
|
|
goto cleanup;
|
|
}
|
|
|
|
kernel_free(read_buf);
|
|
return S_SUCCESS;
|
|
|
|
cleanup:
|
|
kernel_free(read_buf);
|
|
return rv;
|
|
}
|
|
|
|
// Only keep reminders that have not been fired yet.
|
|
static bool prv_reminder_filter(SerializedTimelineItemHeader *hdr, void *context) {
|
|
return ((TimelineItemStatusReminded & hdr->common.status) == 0);
|
|
}
|
|
|
|
status_t reminder_db_next_item_header(TimelineItem *next_item_out) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Finding next item in queue.");
|
|
TimelineItemId id;
|
|
status_t rv = timeline_item_storage_next_item(&s_storage, &id, prv_reminder_filter);
|
|
if (rv) {
|
|
return rv;
|
|
}
|
|
rv = prv_read_item_header(next_item_out, &id);
|
|
return rv;
|
|
}
|
|
|
|
static bool prv_timestamp_title_compare_func(SettingsFile *file, SettingsRecordInfo *info,
|
|
void *context) {
|
|
// Check entry is valid
|
|
if (info->key_len != UUID_SIZE || info->val_len == 0) {
|
|
return true; // continue iteration
|
|
}
|
|
|
|
// Compare timestamps (this should omit most reminders)
|
|
ReminderInfo *reminder_info = (ReminderInfo *)context;
|
|
SerializedTimelineItemHeader header;
|
|
info->get_val(file, (uint8_t *)&header, sizeof(SerializedTimelineItemHeader));
|
|
if (reminder_info->timestamp != header.common.timestamp) {
|
|
return true; // continue iteration
|
|
}
|
|
|
|
// Read the full reminder to compare text
|
|
TimelineItem *reminder = reminder_info->reminder_out;
|
|
if (timeline_item_storage_get_from_settings_record(file, info, reminder) != S_SUCCESS) {
|
|
return true; // continue iteration
|
|
}
|
|
|
|
const char *title = attribute_get_string(&reminder->attr_list, AttributeIdTitle, "");
|
|
if (strcmp(title, reminder_info->title) != 0) {
|
|
timeline_item_free_allocated_buffer(reminder);
|
|
return true; // continue iteration
|
|
}
|
|
|
|
if (reminder_info->filter_cb && !reminder_info->filter_cb(&header, context)) {
|
|
timeline_item_free_allocated_buffer(reminder);
|
|
return true; // continue iteration
|
|
}
|
|
|
|
reminder_info->match = true;
|
|
return false; // stop iteration
|
|
}
|
|
|
|
bool reminder_db_find_by_timestamp_title(time_t timestamp, const char *title,
|
|
TimelineItemStorageFilterCallback filter_cb,
|
|
TimelineItem *reminder_out) {
|
|
PBL_ASSERTN(reminder_out);
|
|
|
|
ReminderInfo reminder_info = {
|
|
.filter_cb = filter_cb,
|
|
.timestamp = timestamp,
|
|
.title = title,
|
|
.reminder_out = reminder_out,
|
|
.match = false
|
|
};
|
|
|
|
timeline_item_storage_each(&s_storage, prv_timestamp_title_compare_func, &reminder_info);
|
|
|
|
return reminder_info.match;
|
|
}
|
|
|
|
static status_t prv_insert_reminder(const uint8_t *key, int key_len,
|
|
const uint8_t *val, int val_len, bool mark_synced) {
|
|
const SerializedTimelineItemHeader *hdr = (const SerializedTimelineItemHeader *)val;
|
|
const bool has_reminded = hdr->common.reminded;
|
|
|
|
status_t rv = timeline_item_storage_insert(&s_storage, key, key_len, val, val_len, mark_synced);
|
|
|
|
char uuid_buffer[UUID_STRING_BUFFER_LENGTH];
|
|
uuid_to_string((Uuid *)key, uuid_buffer);
|
|
PBL_LOG(LOG_LEVEL_INFO, "Reminder added: %s", uuid_buffer);
|
|
|
|
if (rv == S_SUCCESS) {
|
|
if (has_reminded) {
|
|
reminders_handle_reminder_updated(&hdr->common.id);
|
|
} else {
|
|
rv = reminders_update_timer();
|
|
}
|
|
}
|
|
return rv;
|
|
}
|
|
|
|
status_t reminder_db_insert_item(TimelineItem *item) {
|
|
if (item->header.type != TimelineItemTypeReminder) {
|
|
return E_INVALID_ARGUMENT;
|
|
}
|
|
|
|
size_t payload_size = timeline_item_get_serialized_payload_size(item);
|
|
uint8_t *buffer = kernel_malloc_check(sizeof(SerializedTimelineItemHeader) + payload_size);
|
|
timeline_item_serialize_header(item, (SerializedTimelineItemHeader *) buffer);
|
|
timeline_item_serialize_payload(item, buffer + sizeof(SerializedTimelineItemHeader),
|
|
payload_size);
|
|
|
|
// only for items without attributes as of right now
|
|
// Records inserted by the watch are dirty and need to be synced to the phone
|
|
const bool mark_synced = false;
|
|
status_t rv = prv_insert_reminder((uint8_t *)&item->header.id, sizeof(TimelineItemId),
|
|
buffer, sizeof(SerializedTimelineItemHeader) + payload_size, mark_synced);
|
|
|
|
blob_db_sync_record(BlobDBIdReminders, (uint8_t *)&item->header.id, sizeof(TimelineItemId),
|
|
rtc_get_time());
|
|
|
|
kernel_free(buffer);
|
|
return rv;
|
|
}
|
|
|
|
static status_t prv_reminder_db_delete_common(const uint8_t *key, int key_len) {
|
|
status_t rv = timeline_item_storage_delete(&s_storage, key, key_len);
|
|
if (rv == S_SUCCESS) {
|
|
reminders_update_timer();
|
|
}
|
|
|
|
return rv;
|
|
}
|
|
|
|
status_t reminder_db_delete_item(const TimelineItemId *id, bool send_event) {
|
|
return (send_event ? reminder_db_delete :
|
|
prv_reminder_db_delete_common)((uint8_t *)id, sizeof(TimelineItemId));
|
|
}
|
|
|
|
bool reminder_db_is_empty(void) {
|
|
return timeline_item_storage_is_empty(&s_storage);
|
|
}
|
|
|
|
status_t reminder_db_set_status_bits(const TimelineItemId *id, uint8_t status) {
|
|
return timeline_item_storage_set_status_bits(&s_storage, (uint8_t *)id,
|
|
sizeof(ReminderId), status);
|
|
}
|
|
|
|
/////////////////////////
|
|
// Blob DB API
|
|
/////////////////////////
|
|
|
|
void reminder_db_init(void) {
|
|
timeline_item_storage_init(&s_storage,
|
|
REMINDER_DB_FILE_NAME,
|
|
REMINDER_DB_MAX_SIZE,
|
|
MAX_REMINDER_AGE);
|
|
reminders_init();
|
|
}
|
|
|
|
void reminder_db_deinit(void) {
|
|
timeline_item_storage_deinit(&s_storage);
|
|
}
|
|
|
|
status_t reminder_db_insert(const uint8_t *key, int key_len, const uint8_t *val, int val_len) {
|
|
analytics_inc(ANALYTICS_DEVICE_METRIC_REMINDER_RECEIVED_COUNT, AnalyticsClient_System);
|
|
|
|
// Records inserted from the phone are synced
|
|
const bool mark_synced = true;
|
|
return prv_insert_reminder(key, key_len, val, val_len, mark_synced);
|
|
}
|
|
|
|
int reminder_db_get_len(const uint8_t *key, int key_len) {
|
|
return timeline_item_storage_get_len(&s_storage, key, key_len);
|
|
}
|
|
|
|
status_t reminder_db_read(const uint8_t *key, int key_len, uint8_t *val_out, int val_out_len) {
|
|
return timeline_item_storage_read(&s_storage, key, key_len, val_out, val_out_len);
|
|
}
|
|
|
|
status_t reminder_db_delete(const uint8_t *key, int key_len) {
|
|
status_t rv = prv_reminder_db_delete_common(key, key_len);
|
|
reminders_handle_reminder_removed((Uuid *) key);
|
|
|
|
return rv;
|
|
}
|
|
|
|
status_t reminder_db_flush(void) {
|
|
return timeline_item_storage_flush(&s_storage);
|
|
}
|
|
|
|
status_t reminder_db_is_dirty(bool *is_dirty_out) {
|
|
*is_dirty_out = false;
|
|
return timeline_item_storage_each(&s_storage, sync_util_is_dirty_cb, is_dirty_out);
|
|
}
|
|
|
|
BlobDBDirtyItem* reminder_db_get_dirty_list(void) {
|
|
BlobDBDirtyItem *dirty_list = NULL;
|
|
timeline_item_storage_each(&s_storage, sync_util_build_dirty_list_cb, &dirty_list);
|
|
|
|
return dirty_list;
|
|
}
|
|
|
|
status_t reminder_db_mark_synced(const uint8_t *key, int key_len) {
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "reminder_db_mark_synced");
|
|
return timeline_item_storage_mark_synced(&s_storage, key, key_len);
|
|
}
|