Add new generic LRU cache to the client
This commit is contained in:
parent
41d061c8ff
commit
73159ba091
6 changed files with 197 additions and 0 deletions
|
|
@ -91,6 +91,7 @@ src/client/sidebar/sidebar-count-cell-renderer.vala
|
|||
src/client/sidebar/sidebar-entry.vala
|
||||
src/client/sidebar/sidebar-tree.vala
|
||||
src/client/util/util-avatar.vala
|
||||
src/client/util/util-cache.vala
|
||||
src/client/util/util-date.vala
|
||||
src/client/util/util-email.vala
|
||||
src/client/util/util-files.vala
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ geary_client_vala_sources = files(
|
|||
'sidebar/sidebar-tree.vala',
|
||||
|
||||
'util/util-avatar.vala',
|
||||
'util/util-cache.vala',
|
||||
'util/util-date.vala',
|
||||
'util/util-email.vala',
|
||||
'util/util-files.vala',
|
||||
|
|
|
|||
122
src/client/util/util-cache.vala
Normal file
122
src/client/util/util-cache.vala
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/** A simple least-recently-used cache. */
|
||||
public class Util.Cache.Lru<T> : Geary.BaseObject {
|
||||
|
||||
|
||||
private class CacheEntry<T> {
|
||||
|
||||
|
||||
public static int lru_compare(CacheEntry<T> a, CacheEntry<T> b) {
|
||||
return (a.key == b.key)
|
||||
? 0 : (int) (a.last_used - b.last_used);
|
||||
}
|
||||
|
||||
|
||||
public string key;
|
||||
public T value;
|
||||
public int64 last_used;
|
||||
|
||||
|
||||
public CacheEntry(string key, T value, int64 last_used) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.last_used = last_used;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Specifies the maximum number of cache entries to be stored. */
|
||||
public uint max_size { get; set; }
|
||||
|
||||
/** Determines if the cache has any entries or not. */
|
||||
public bool is_empty {
|
||||
get { return this.cache.is_empty; }
|
||||
}
|
||||
|
||||
/** Determines the current number of cache entries. */
|
||||
public uint size {
|
||||
get { return this.cache.size; }
|
||||
}
|
||||
|
||||
private Gee.Map<string,CacheEntry<T>> cache =
|
||||
new Gee.HashMap<string,CacheEntry<T>>();
|
||||
private Gee.SortedSet<CacheEntry<T>> ordering =
|
||||
new Gee.TreeSet<CacheEntry<T>>(CacheEntry.lru_compare);
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new least-recently-used cache with the given size.
|
||||
*/
|
||||
public Lru(uint max_size) {
|
||||
this.max_size = max_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an entry in the cache, replacing any existing entry.
|
||||
*
|
||||
* The entry is added to the back of the removal queue. If adding
|
||||
* the entry causes the size of the cache to exceed the maximum,
|
||||
* the entry at the front of the queue will be evicted.
|
||||
*/
|
||||
public void set_entry(string key, T value) {
|
||||
int64 now = GLib.get_monotonic_time();
|
||||
CacheEntry<T> entry = new CacheEntry<T>(key, value, now);
|
||||
this.cache.set(key, entry);
|
||||
this.ordering.add(entry);
|
||||
|
||||
// Prune if needed
|
||||
if (this.cache.size > this.max_size) {
|
||||
CacheEntry oldest = this.ordering.first();
|
||||
this.cache.unset(oldest.key);
|
||||
this.ordering.remove(oldest);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the entry from the cache, if found.
|
||||
*
|
||||
* If the entry was found, it is move to the back of the removal
|
||||
* queue.
|
||||
*/
|
||||
public T get_entry(string key) {
|
||||
int64 now = GLib.get_monotonic_time();
|
||||
CacheEntry<T>? entry = this.cache.get(key);
|
||||
T value = null;
|
||||
if (entry != null) {
|
||||
value = entry.value;
|
||||
// Need to remove the entry from the ordering before
|
||||
// updating the last used time since doing so changes the
|
||||
// ordering
|
||||
this.ordering.remove(entry);
|
||||
entry.last_used = now;
|
||||
this.ordering.add(entry);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Removes an entry from the cache and returns it, if found. */
|
||||
public T remove_entry(string key) {
|
||||
CacheEntry<T>? entry = null;
|
||||
T value = null;
|
||||
this.cache.unset(key, out entry);
|
||||
if (entry != null) {
|
||||
this.ordering.remove(entry);
|
||||
value = entry.value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/** Evicts all entries in the cache. */
|
||||
public void clear() {
|
||||
this.cache.clear();
|
||||
this.ordering.clear();
|
||||
}
|
||||
|
||||
}
|
||||
71
test/client/util/util-cache-test.vala
Normal file
71
test/client/util/util-cache-test.vala
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
public class Util.Cache.Test : TestCase {
|
||||
|
||||
public Test() {
|
||||
base("UtilCacheTest");
|
||||
add_test("lru_insertion", lru_insertion);
|
||||
add_test("lru_eviction", lru_eviction);
|
||||
}
|
||||
|
||||
public void lru_insertion() throws GLib.Error {
|
||||
const string A = "a";
|
||||
const string B = "b";
|
||||
const string C = "c";
|
||||
const string D = "d";
|
||||
|
||||
Lru<string> test_article = new Lru<string>(2);
|
||||
|
||||
assert_true(test_article.is_empty);
|
||||
assert_uint(0, test_article.size);
|
||||
|
||||
assert_true(test_article.get_entry(A) == null);
|
||||
test_article.set_entry(A, A);
|
||||
assert_string(A, test_article.get_entry(A));
|
||||
|
||||
assert_false(test_article.is_empty);
|
||||
assert_uint(1, test_article.size);
|
||||
|
||||
test_article.set_entry(B, B);
|
||||
assert_string(B, test_article.get_entry(B));
|
||||
assert_uint(2, test_article.size);
|
||||
|
||||
test_article.set_entry(C, C);
|
||||
assert_string(C, test_article.get_entry(C));
|
||||
assert_uint(2, test_article.size);
|
||||
assert_true(test_article.get_entry(A) == null);
|
||||
|
||||
test_article.set_entry(D, D);
|
||||
assert_string(D, test_article.get_entry(D));
|
||||
assert_uint(2, test_article.size);
|
||||
assert_true(test_article.get_entry(B) == null);
|
||||
|
||||
test_article.clear();
|
||||
assert_true(test_article.is_empty);
|
||||
assert_uint(0, test_article.size);
|
||||
}
|
||||
|
||||
public void lru_eviction() throws GLib.Error {
|
||||
const string A = "a";
|
||||
const string B = "b";
|
||||
const string C = "c";
|
||||
|
||||
Lru<string> test_article = new Lru<string>(2);
|
||||
|
||||
test_article.set_entry(A, A);
|
||||
test_article.set_entry(B, B);
|
||||
|
||||
test_article.get_entry(A);
|
||||
test_article.set_entry(C, C);
|
||||
|
||||
assert_string(C, test_article.get_entry(C));
|
||||
assert_string(A, test_article.get_entry(A));
|
||||
assert_true(test_article.get_entry(B) == null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -78,6 +78,7 @@ geary_test_client_sources = [
|
|||
'client/components/client-web-view-test-case.vala',
|
||||
'client/composer/composer-web-view-test.vala',
|
||||
'client/util/util-avatar-test.vala',
|
||||
'client/util/util-cache-test.vala',
|
||||
'client/util/util-email-test.vala',
|
||||
|
||||
'js/client-page-state-test.vala',
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ int main(string[] args) {
|
|||
client.add_suite(new ComposerWebViewTest().get_suite());
|
||||
client.add_suite(new ConfigurationTest().get_suite());
|
||||
client.add_suite(new Util.Avatar.Test().get_suite());
|
||||
client.add_suite(new Util.Cache.Test().get_suite());
|
||||
client.add_suite(new Util.Email.Test().get_suite());
|
||||
|
||||
TestSuite js = new TestSuite("js");
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue