mirror of
https://github.com/google/pebble.git
synced 2025-05-05 09:21:40 -04:00
604 lines
19 KiB
C
604 lines
19 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 "app_cache.h"
|
|
|
|
#include "kernel/events.h"
|
|
#include "kernel/pbl_malloc.h"
|
|
#include "kernel/pebble_tasks.h"
|
|
#include "process_management/app_install_manager.h"
|
|
#include "process_management/app_storage.h"
|
|
#include "services/common/system_task.h"
|
|
#include "services/normal/blob_db/pin_db.h"
|
|
#include "services/normal/filesystem/app_file.h"
|
|
#include "services/normal/filesystem/pfs.h"
|
|
#include "services/normal/settings/settings_file.h"
|
|
#include "services/normal/settings/settings_file.h"
|
|
#include "shell/normal/quick_launch.h"
|
|
#include "shell/normal/watchface.h"
|
|
#include "shell/prefs.h"
|
|
#include "system/logging.h"
|
|
#include "system/passert.h"
|
|
#include "util/attributes.h"
|
|
#include "util/list.h"
|
|
#include "util/math.h"
|
|
#include "util/time/time.h"
|
|
#include "util/units.h"
|
|
|
|
//! @file app_cache.c
|
|
//! App Cache
|
|
|
|
//! The App Cache keeps track of the install date, last launch, launch count, and size of an
|
|
//! application.
|
|
//!
|
|
//! A priority can also be calculated for each entry. It is calculated by a simple last used
|
|
//! algorithm (TODO Improve: PBL-13209) which will help determine which application
|
|
//! needs to be evicted in order to free up more space for other application binaries.
|
|
//!
|
|
//! When an entry is added into the app cache, it means the binaries now reside on the watch. On
|
|
//! this function call, a callback is initiated to check if we need to free space for a possible
|
|
//! future application. If so, the applications with the lowest priority that add up to or are
|
|
//! greater than the space needed will be removed.
|
|
//!
|
|
//! It is assumed that there will ALWAYS be space for a single application of maximum size based
|
|
//! on the platform. The only time when this isn't true is the time between "add_entry" and the
|
|
//! callback to clean up the cache.
|
|
|
|
#define APP_CACHE_FILE_NAME "appcache"
|
|
|
|
//! each cache entry is ~16 bytes, 4000 / 16 = 250 apps
|
|
#define APP_CACHE_MAX_SIZE 4000
|
|
|
|
//! Keep enough room for the maximum sized application based on platform, plus a little more room.
|
|
//! Source: https://pebbletechnology.atlassian.net/wiki/display/DEV/PBW+3.0
|
|
#if PLATFORM_TINTIN || PLATFORM_SILK || UNITTEST
|
|
#define APP_SPACE_BUFFER KiBYTES(300)
|
|
#else
|
|
#define APP_SPACE_BUFFER MiBYTES(4)
|
|
#endif
|
|
|
|
#define MAX_PRIORITY ((uint32_t)~0)
|
|
|
|
// 4 quick launch apps, 1 default watchface, 1 default worker
|
|
#define DO_NOT_EVICT_LIST_SIZE (NUM_BUTTONS + 2)
|
|
|
|
static PebbleRecursiveMutex *s_app_cache_mutex = NULL;
|
|
|
|
//! Actual data structure stored in flash about an app cache entry
|
|
typedef struct PACKED {
|
|
time_t install_date;
|
|
time_t last_launch;
|
|
uint32_t total_size;
|
|
uint16_t launch_count;
|
|
} AppCacheEntry;
|
|
|
|
typedef struct {
|
|
ListNode node;
|
|
AppInstallId id;
|
|
uint32_t size;
|
|
uint32_t priority;
|
|
} EvictListNode;
|
|
|
|
typedef struct {
|
|
EvictListNode *list;
|
|
uint32_t bytes_needed;
|
|
uint32_t bytes_in_list;
|
|
const AppInstallId do_not_evict[DO_NOT_EVICT_LIST_SIZE];
|
|
} EachEvictData;
|
|
|
|
//! Takes the information given in entry and calculates a new priority for the app.
|
|
//!
|
|
//! Policy rules:
|
|
//! 1. App that has least recently launched or been installed app is evicted.
|
|
static uint32_t prv_calculate_priority(AppCacheEntry *entry) {
|
|
return (uint32_t) MAX(entry->last_launch, entry->install_date);
|
|
}
|
|
|
|
//! Comparator for EvictListNode
|
|
static int evict_node_comparator(void *a, void *b) {
|
|
EvictListNode *a_node = (EvictListNode *)a;
|
|
EvictListNode *b_node = (EvictListNode *)b;
|
|
|
|
if (b_node->priority > a_node->priority) {
|
|
return 1;
|
|
} else if (b_node->priority < a_node->priority) {
|
|
return -1;
|
|
} else {
|
|
// bigger applications to have a lower priority
|
|
if (b_node->size < a_node->size) {
|
|
return 1;
|
|
} else if (b_node->size > a_node->size) {
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//! Trim the applications with highest priority while still keeping (bytes_in_list > bytes_needed)
|
|
static void prv_trim_top_priorities(EvictListNode **list_node, uint32_t *bytes_in_list,
|
|
uint32_t bytes_needed) {
|
|
EvictListNode *node = *list_node;
|
|
while (node) {
|
|
EvictListNode *temp = node;
|
|
if (node->size <= (*bytes_in_list - bytes_needed)) {
|
|
*bytes_in_list -= node->size;
|
|
node = (EvictListNode *)list_pop_head((ListNode *)node);
|
|
kernel_free(temp);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
*list_node = node;
|
|
}
|
|
|
|
//! Check if we need to free up some space in the cache. If so, do it.
|
|
static void prv_cleanup_app_cache_if_needed(void *data) {
|
|
uint32_t pfs_space = get_available_pfs_space();
|
|
|
|
if (pfs_space < APP_SPACE_BUFFER) {
|
|
const uint32_t to_free = (APP_SPACE_BUFFER - pfs_space);
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Cache OOS: Need to free %"PRIu32" bytes, PFS avail space: %"PRIu32"",
|
|
to_free, pfs_space);
|
|
app_cache_free_up_space(to_free);
|
|
}
|
|
}
|
|
|
|
static void prv_delete_cache_callback(void *data) {
|
|
app_cache_flush();
|
|
}
|
|
|
|
static void prv_delete_cached_files(void) {
|
|
pfs_remove_files(is_app_file_name);
|
|
}
|
|
|
|
static bool prv_is_in_list(AppInstallId id, const AppInstallId list[], uint8_t len) {
|
|
for (unsigned int i = 0; i < len; i++) {
|
|
if (list[i] == id) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////
|
|
// Settings Helpers
|
|
//////////////////////
|
|
|
|
//! Settings iterator function that finds the entry with the lowest calculated priority
|
|
static bool prv_each_free_up_space(SettingsFile *file, SettingsRecordInfo *info, void *context) {
|
|
// check entry is valid
|
|
if ((info->key_len != sizeof(AppInstallId)) || (info->val_len != sizeof(AppCacheEntry))) {
|
|
PBL_LOG(LOG_LEVEL_WARNING,
|
|
"Invalid cache entry with key_len: %u and val_len: %u, flushing",
|
|
info->key_len, info->val_len);
|
|
system_task_add_callback(prv_delete_cache_callback, NULL);
|
|
return false; // stop iterating, delete the file and binaries
|
|
}
|
|
|
|
EachEvictData *data = (EachEvictData *)context;
|
|
|
|
AppInstallId id;
|
|
AppCacheEntry entry;
|
|
|
|
info->get_key(file, (uint8_t *)&id, info->key_len);
|
|
info->get_val(file, (uint8_t *)&entry, info->val_len);
|
|
|
|
// create node
|
|
EvictListNode *node = kernel_malloc_check(sizeof(EvictListNode));
|
|
list_init((ListNode *)node);
|
|
|
|
// give them an extremely high priority so that we only remove them if we really NEED to
|
|
// This list contains defaults that we shouldn't be removing.
|
|
uint32_t priority = 0;
|
|
if (prv_is_in_list(id, data->do_not_evict, DO_NOT_EVICT_LIST_SIZE)) {
|
|
priority = MAX_PRIORITY;
|
|
}
|
|
|
|
*node = (EvictListNode) {
|
|
.id = id,
|
|
.size = entry.total_size,
|
|
.priority = MAX(priority, prv_calculate_priority(&entry)),
|
|
};
|
|
|
|
data->list = (EvictListNode *)list_sorted_add((ListNode *)data->list, (ListNode *)node,
|
|
evict_node_comparator, false);
|
|
data->bytes_in_list += node->size;
|
|
|
|
if (data->bytes_in_list > data->bytes_needed) {
|
|
prv_trim_top_priorities(&data->list, &data->bytes_in_list, data->bytes_needed);
|
|
}
|
|
|
|
return true; // continue iterating
|
|
}
|
|
|
|
//////////////////////////
|
|
// AppCache API's
|
|
//////////////////////////
|
|
|
|
//! Updates metadata within the cache entry for the given AppInstallId. Will update such fields as
|
|
//! launch count, last launch, and priority
|
|
status_t app_cache_app_launched(AppInstallId app_id) {
|
|
status_t rv;
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
SettingsFile file;
|
|
rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
|
|
if (rv != S_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
AppCacheEntry entry = { 0 };
|
|
rv = settings_file_get(&file, (uint8_t *)&app_id, sizeof(AppInstallId),
|
|
(uint8_t *)&entry, sizeof(AppCacheEntry));
|
|
|
|
if (rv == S_SUCCESS) {
|
|
entry.last_launch = rtc_get_time();
|
|
entry.launch_count += 1;
|
|
|
|
rv = settings_file_set(&file, (uint8_t *)&app_id, sizeof(AppInstallId),
|
|
(uint8_t *)&entry, sizeof(AppCacheEntry));
|
|
} else {
|
|
app_storage_delete_app(app_id);
|
|
settings_file_delete(&file, (uint8_t *)&app_id, sizeof(AppInstallId));
|
|
}
|
|
|
|
settings_file_close(&file);
|
|
}
|
|
unlock:
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
return rv;
|
|
}
|
|
|
|
//! Asks the app cache to remove 'bytes_needed' bytes of application binaries to free up space
|
|
//! for other things.
|
|
status_t app_cache_free_up_space(uint32_t bytes_needed) {
|
|
if (bytes_needed == 0) {
|
|
return E_INVALID_ARGUMENT;
|
|
}
|
|
|
|
status_t rv;
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
SettingsFile file;
|
|
rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
|
|
if (rv != S_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
// we don't want to remove any default apps or quick launch apps, so keep them in a list.
|
|
EachEvictData evict_data = (EachEvictData) {
|
|
.bytes_needed = bytes_needed,
|
|
.do_not_evict = {
|
|
#if !SHELL_SDK
|
|
quick_launch_get_app(BUTTON_ID_UP),
|
|
quick_launch_get_app(BUTTON_ID_SELECT),
|
|
quick_launch_get_app(BUTTON_ID_DOWN),
|
|
quick_launch_get_app(BUTTON_ID_BACK),
|
|
#endif
|
|
watchface_get_default_install_id(),
|
|
worker_preferences_get_default_worker(),
|
|
},
|
|
};
|
|
|
|
settings_file_each(&file, prv_each_free_up_space, &evict_data);
|
|
settings_file_close(&file);
|
|
|
|
// remove all nodes found
|
|
EvictListNode *node = evict_data.list;
|
|
while (node) {
|
|
EvictListNode *temp = node;
|
|
PBL_LOG(LOG_LEVEL_DEBUG, "Deleting application binaries for app id: %"PRIu32", size: %"PRIu32,
|
|
node->id, node->size);
|
|
app_cache_remove_entry(node->id);
|
|
node = (EvictListNode *)list_pop_head((ListNode *)node);
|
|
kernel_free(temp);
|
|
}
|
|
}
|
|
unlock:
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
return rv;
|
|
}
|
|
|
|
//////////////////////
|
|
// AppCache Helpers
|
|
//////////////////////
|
|
|
|
// Remove the filename entry in the PFSFileList (via context) that corresponds to the
|
|
// app install id passed in via info
|
|
static bool prv_remove_matching_resource_file_callback(SettingsFile *file,
|
|
SettingsRecordInfo *info,
|
|
void *context) {
|
|
AppInstallId id;
|
|
// examine the SettingsRecordInfo and extract the AppInstallId from it
|
|
info->get_key(file, (uint8_t *)&id, info->key_len);
|
|
// the context passed in is really a pointer to the resource_list
|
|
PFSFileListEntry **resource_list = context;
|
|
PFSFileListEntry *iter = *resource_list;
|
|
while (iter) {
|
|
// grab the next entry right now since we may delete the node we're looking at
|
|
PFSFileListEntry *next = (PFSFileListEntry *)iter->list_node.next;
|
|
if (app_file_parse_app_id(iter->name) == id) {
|
|
// the AppInstallId of the file matches the one in the cache so we can remove this
|
|
// entry from the resource_list (since we don't want to delete it)
|
|
// note: resource_list may be updated if we happen to remove the first entry in the list
|
|
list_remove(&(iter->list_node), (ListNode**)resource_list, NULL);
|
|
kernel_free(iter); // free up the memory for the node we just removed
|
|
break; // we can quit now that we've found a match for this id
|
|
}
|
|
iter = next;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Delete files from resource_list that don't correspond to entries in the app cache
|
|
static void prv_app_cache_find_and_delete_orphans(PFSFileListEntry **resource_list) {
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
|
|
SettingsFile file;
|
|
status_t rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
|
|
if (rv != S_SUCCESS) {
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
return;
|
|
}
|
|
// resource_list contains all of the resource files we found. We only
|
|
// want to delete orphans so we can remove any entries from the list that correspond
|
|
// to items in the app cache...
|
|
// prv_remove_matching_resource_file_callback scans resource_list and removes the entry
|
|
// corresponding to the passed-in application's id
|
|
settings_file_each(&file, prv_remove_matching_resource_file_callback, resource_list);
|
|
settings_file_close(&file);
|
|
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
|
|
// resource_list now only contains filenames of resource files that don't have corresponding
|
|
// entries in the app cache. We can safely delete these files.
|
|
PFSFileListEntry *iter = *resource_list;
|
|
while (iter) {
|
|
PBL_LOG(LOG_LEVEL_INFO, "Orphaned resource file removed: %s", iter->name);
|
|
pfs_remove(iter->name);
|
|
iter = (PFSFileListEntry *)iter->list_node.next;
|
|
}
|
|
}
|
|
|
|
// The bug addressed in PBL-34010 caused resource files to remain in the filesystem even
|
|
// after the associated application had been deleted. This function attempts to find such
|
|
// orphaned files and remove them. Note: further to the bug in PBL-34010, this function will
|
|
// remove any resource files that are not related to apps currently in the cache.
|
|
static void prv_purge_orphaned_resource_files(void) {
|
|
// create a list of all app resource files in the filesystem
|
|
PFSFileListEntry *resource_files = pfs_create_file_list(is_app_resource_file_name);
|
|
// delete app resource files that don't correspond to entries in the app cache
|
|
prv_app_cache_find_and_delete_orphans(&resource_files);
|
|
pfs_delete_file_list(resource_files);
|
|
}
|
|
|
|
//////////////////////////
|
|
// AppCache Settings API's
|
|
//////////////////////////
|
|
|
|
//! Set up the app cache
|
|
void app_cache_init(void) {
|
|
s_app_cache_mutex = mutex_create_recursive();
|
|
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
// if no cache file exists, then we should go ahead and clean up any files that are left over
|
|
int fd = pfs_open(APP_CACHE_FILE_NAME, OP_FLAG_READ, FILE_TYPE_STATIC, 0);
|
|
if (fd < 0) {
|
|
prv_delete_cached_files();
|
|
goto unlock;
|
|
}
|
|
pfs_close(fd);
|
|
}
|
|
|
|
unlock:
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
|
|
prv_purge_orphaned_resource_files();
|
|
}
|
|
|
|
//! Adds an entry with the given AppInstallId to the cache
|
|
status_t app_cache_add_entry(AppInstallId app_id, uint32_t total_size) {
|
|
status_t rv;
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
SettingsFile file;
|
|
rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
|
|
if (rv != S_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
AppCacheEntry entry = {
|
|
.install_date = rtc_get_time(),
|
|
.last_launch = 0,
|
|
.launch_count = 0,
|
|
.total_size = total_size,
|
|
};
|
|
|
|
rv = settings_file_set(&file, (uint8_t *)&app_id, sizeof(AppInstallId),
|
|
(uint8_t *)&entry, sizeof(AppCacheEntry));
|
|
|
|
settings_file_close(&file);
|
|
|
|
// cleanup the cache if we need to
|
|
system_task_add_callback(prv_cleanup_app_cache_if_needed, NULL);
|
|
}
|
|
unlock:
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
return rv;
|
|
}
|
|
|
|
//! Tests if an entry with the given AppInstallId is in the cache
|
|
bool app_cache_entry_exists(AppInstallId app_id) {
|
|
bool exists = false;
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
SettingsFile file;
|
|
status_t rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
|
|
if (rv != S_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
exists = settings_file_exists(&file, (uint8_t *)&app_id, sizeof(AppInstallId));
|
|
|
|
if (exists && !app_storage_app_exists(app_id)) {
|
|
settings_file_delete(&file, (uint8_t *)&app_id, sizeof(AppInstallId));
|
|
exists = false;
|
|
}
|
|
|
|
settings_file_close(&file);
|
|
}
|
|
unlock:
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
return exists;
|
|
}
|
|
|
|
//! Removes an entry with the given AppInstallId from the cache
|
|
status_t app_cache_remove_entry(AppInstallId app_id) {
|
|
status_t rv;
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
SettingsFile file;
|
|
rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
|
|
if (rv != S_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
rv = settings_file_delete(&file, (uint8_t *)&app_id, sizeof(AppInstallId));
|
|
if (rv == S_SUCCESS) {
|
|
// Will delete an app from the filesystem.
|
|
app_storage_delete_app(app_id);
|
|
}
|
|
|
|
settings_file_close(&file);
|
|
}
|
|
|
|
if (rv == S_SUCCESS) {
|
|
PebbleEvent e = {
|
|
.type = PEBBLE_APP_CACHE_EVENT,
|
|
.app_cache_event = {
|
|
.cache_event_type = PebbleAppCacheEvent_Removed,
|
|
.install_id = app_id,
|
|
},
|
|
};
|
|
event_put(&e);
|
|
}
|
|
unlock:
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
return rv;
|
|
}
|
|
|
|
void app_cache_flush(void) {
|
|
PBL_ASSERT_TASK(PebbleTask_KernelBackground);
|
|
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
pfs_remove(APP_CACHE_FILE_NAME);
|
|
prv_delete_cached_files();
|
|
}
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
}
|
|
|
|
////////////////////////////////
|
|
// Testing only
|
|
////////////////////////////////
|
|
|
|
static bool prv_each_get_size(SettingsFile *file, SettingsRecordInfo *info, void *context) {
|
|
if ((info->key_len != sizeof(AppInstallId)) || (info->val_len != sizeof(AppCacheEntry))) {
|
|
return true; // continue iterating
|
|
}
|
|
|
|
uint32_t *cache_size = (uint32_t *)context;
|
|
AppCacheEntry entry;
|
|
info->get_val(file, (uint8_t *)&entry, info->val_len);
|
|
*cache_size += entry.total_size;
|
|
|
|
return true; // continue iterating
|
|
}
|
|
|
|
uint32_t app_cache_get_size(void) {
|
|
uint32_t cache_size = 0;
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
SettingsFile file;
|
|
status_t rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
|
|
if (rv != S_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
settings_file_each(&file, prv_each_get_size, &cache_size);
|
|
settings_file_close(&file);
|
|
}
|
|
unlock:
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
return cache_size;
|
|
}
|
|
|
|
typedef struct {
|
|
AppInstallId id;
|
|
uint32_t priority;
|
|
} AppCacheEachData;
|
|
|
|
//! Settings iterator function that finds the entry with the lowest calculated priority
|
|
static bool prv_each_min_priority(SettingsFile *file, SettingsRecordInfo *info, void *context) {
|
|
// check entry is valid
|
|
if ((info->key_len != sizeof(AppInstallId)) || (info->val_len != sizeof(AppCacheEntry))) {
|
|
return true; // continue iterating
|
|
}
|
|
|
|
AppCacheEachData *to_evict = (AppCacheEachData *)context;
|
|
|
|
AppInstallId id;
|
|
AppCacheEntry entry;
|
|
|
|
info->get_key(file, (uint8_t *)&id, info->key_len);
|
|
info->get_val(file, (uint8_t *)&entry, info->val_len);
|
|
|
|
uint32_t entry_priority = prv_calculate_priority(&entry);
|
|
if (entry_priority < to_evict->priority) {
|
|
to_evict->id = id;
|
|
to_evict->priority = entry_priority;
|
|
}
|
|
|
|
return true; // continue iterating
|
|
}
|
|
|
|
//! Find the entry in the app cache with the lowest calculated priority
|
|
AppInstallId app_cache_get_next_eviction(void) {
|
|
AppInstallId ret_value = INSTALL_ID_INVALID;
|
|
mutex_lock_recursive(s_app_cache_mutex);
|
|
{
|
|
SettingsFile file;
|
|
status_t rv = settings_file_open(&file, APP_CACHE_FILE_NAME, APP_CACHE_MAX_SIZE);
|
|
if (rv != S_SUCCESS) {
|
|
goto unlock;
|
|
}
|
|
|
|
// set max so that any application will have a lower priority.
|
|
AppCacheEachData to_evict = {
|
|
.id = INSTALL_ID_INVALID,
|
|
.priority = MAX_PRIORITY,
|
|
};
|
|
settings_file_each(&file, prv_each_min_priority, (void *)&to_evict);
|
|
|
|
settings_file_close(&file);
|
|
ret_value = to_evict.id;
|
|
}
|
|
unlock:
|
|
mutex_unlock_recursive(s_app_cache_mutex);
|
|
return ret_value;
|
|
}
|