An asynchronous, persistent key-value store
C# Other
Latest commit 6df3c24 Aug 22, 2016 @flagbug flagbug committed on GitHub Merge pull request #311 from ghuntley/issue-pr-templates
docs: added issue/pr templates and contrib guidelines
Permalink
Failed to load latest commit information.
.github docs: added issue/pr templates and contrib guidelines Aug 15, 2016
.nuget Sorry Haacked, I can't stand it anymore Jun 13, 2014
Akavache.Deprecated Version bump to 4.1.2 Oct 27, 2015
Akavache.Mobile Revert "Remove the support for WinRT80, since it can't even be opened… May 24, 2016
Akavache.Sqlite3 Index the TypeName in the database to make bulk object queries faster May 23, 2016
Akavache.Tests Allow UTC DateTimes to be forced. Oct 27, 2015
Akavache Revert "Remove the support for WinRT80, since it can't even be opened… May 24, 2016
component MonoMac only Feb 8, 2014
packages Merge remote-tracking branch 'origin/pr/163' Jul 24, 2014
.gitattributes Update .gitignore and .gitattributes. Jul 19, 2013
.gitignore DS_Store is a crime Jul 18, 2014
.gitmodules Add Pierce Nov 11, 2013
Akavache.6.0.ReSharper Initial import Dec 2, 2011
Akavache.6.1.ReSharper Add R# 6.1 file Jan 6, 2012
Akavache.sln Revert "Remove the support for WinRT80, since it can't even be opened… May 24, 2016
Akavache_VSAll.sln Revert "Remove the support for WinRT80, since it can't even be opened… May 24, 2016
Akavache_XSAll.sln Project nonsense Jan 3, 2015
Akavache_XSNoMac.sln Merge remote-tracking branch 'origin/akavache4-master' into akavache-… Dec 24, 2013
BuildPC.cmd Update release scripts to build Dec 27, 2013
CODE_OF_CONDUCT.md Update CODE_OF_CONDUCT.md Jan 22, 2016
CONTRIBUTING.md docs: added issue/pr templates and contrib guidelines Aug 15, 2016
CleanFolders.ps1 Update packages. Jul 23, 2014
CommonAssemblyInfo.cs Version bump to 4.1.2 Oct 27, 2015
LICENSE Add LICENSE Apr 24, 2012
Logo.psd Add a logo for Akavache Jul 20, 2013
MakeRelease.ps1 Revert "Remove the support for WinRT80, since it can't even be opened… May 24, 2016
Makefile Package restore in Makefile Jul 17, 2014
PACKAGING.md Update some documentation Jul 20, 2013
README.md Add Shutdown docs to README. May 27, 2016
Rebracer.xml Add rebracer file. Jul 18, 2014
akavache-meta.nuspec Version bump to 4.1.2 Oct 27, 2015

README.md

Akavache: An Asynchronous Key-Value Store for Native Applications

Akavache is an asynchronous, persistent (i.e. writes to disk) key-value store created for writing desktop and mobile applications in C#, based on SQLite3. Akavache is great for both storing important data (i.e. user settings) as well as cached local data that expires.

Dat Logo

Where can I use it?

Akavache is currently compatible with:

  • Xamarin.iOS / Xamarin.Mac 32-bit
  • Xamarin.Android
  • .NET 4.5 Desktop (WPF)
  • Windows Phone 8
  • WinRT (Windows Store)
  • Windows Phone 8.1 Universal Apps

What does that mean?

Downloading and storing remote data from the internet while still keeping the UI responsive is a task that nearly every modern application needs to do. However, many applications that don't take the consideration of caching into the design from the start often end up with inconsistent, duplicated code for caching different types of objects.

Akavache is a library that makes common app patterns easy, and unifies caching of different object types (i.e. HTTP responses vs. JSON objects vs. images).

It's built on a core key-value byte array store (conceptually similar to a Dictionary<string, byte[]>), and on top of that store, extensions are added to support:

  • Arbitrary objects via JSON.NET
  • Fetching and loading Images and URLs from the Internet
  • Storing and automatically encrypting User Credentials

Platform-specific notes

  • Xamarin.iOS / Xamarin.Mac 32-bit - No issues.

  • Xamarin.Android - No issues.

  • .NET 4.5 Desktop (WPF) - No issues

  • Windows Phone 8.0 - You must mark your application as x86 or ARM, or else you will get a strange runtime error about SQLitePCL_Raw not loading correctly.

  • WinRT (Windows Store) - You must mark your application as x86 or ARM, or else you will get a strange runtime error about SQLitePCL_Raw not loading correctly. You must also ensure that the Microsoft Visual C++ runtime is added to your project. This means that you must submit several versions of your app to the Store to support ARM.

  • Windows Phone 8.1 Universal Apps - You must mark your application as x86 or ARM, or else you will get a strange runtime error about SQLitePCL_Raw not loading correctly. You must also ensure that the Microsoft Visual C++ runtime is added to your project.

Getting Started

Interacting with Akavache is primarily done through an object called BlobCache. At App startup, you must first set your app's name via BlobCache.ApplicationName - on the desktop, your application's data will be stored in %AppData%\[ApplicationName] and %LocalAppData%\[ApplicationName]. Store data that should be shared between different machines in BlobCache.UserAccount and store data that is throwaway or per-machine (such as images) in BlobCache.LocalMachine.

The most straightforward way to use Akavache is via the object extensions:

using System.Reactive.Linq;   // IMPORTANT - this makes await work!

// Make sure you set the application name before doing any inserts or gets
BlobCache.ApplicationName = "AkavacheExperiment";

var myToaster = new Toaster();
await BlobCache.UserAccount.InsertObject("toaster", myToaster);

//
// ...later, in another part of town...
//

// Using async/await
var toaster = await BlobCache.UserAccount.GetObject<Toaster>("toaster");

// or without async/await
Toaster toaster;

BlobCache.UserAccount.GetObject<Toaster>("toaster")
    .Subscribe(x => toaster = x, ex => Console.WriteLine("No Key!"));

Handling Errors

When a key is not present in the cache, GetObject throws a KeyNotFoundException (or more correctly, OnError's the IObservable). Often, you would want to return a default value instead of failing:

Toaster toaster;

try {
    toaster = await BlobCache.UserAccount.GetObjectAsync("toaster");
} catch (KeyNotFoundException ex) {
    toaster = new Toaster();
}

// Or without async/await:
toaster = await BlobCache.UserAccount.GetObjectAsync<Toaster>("toaster")
    .Catch(Observable.Return(new Toaster()));

Shutting Down

Critical to the integrity of your Akavache cache is the BlobCache.Shutdown() method. You must call this when your application shuts down. Moreover, be sure to wait for the result:

BlobCache.Shutdown().Wait();

Failure to do this may mean that queued items are not flushed to the cache.

Examining Akavache caches

Using Akavache Explorer, you can dig into Akavache repos for debugging purposes to see what has been stored.

What's this Global Variable nonsense? Why can't I use $FAVORITE_IOC_LIBRARY

You totally can. Just instantiate SQLitePersistentBlobCache or SQLiteEncryptedBlobCache instead - the static variables are there just to make it easier to get started.

Basic Method Documentation

Every blob cache supports the basic raw operations given below (some of them are not implemented directly, but are added on via extension methods):

/*
 * Get items from the store
 */

// Get a single item
IObservable<byte[]> Get(string key);

// Get a list of items
IObservable<IDictionary<string, byte[]>> Get(IEnumerable<string> keys);

// Get an object serialized via InsertObject
IObservable<T> GetObject<T>(string key);

// Get all objects of type T
IObservable<IEnumerable<T>> GetAllObjects<T>();

// Get a list of objects given a list of keys
IObservable<IDictionary<string, T>> GetObjects<T>(IEnumerable<string> keys);

/*
 * Save items to the store
 */

// Insert a single item
IObservable<Unit> Insert(string key, byte[] data, DateTimeOffset? absoluteExpiration = null);

// Insert a set of items
IObservable<Unit> Insert(IDictionary<string, byte[]> keyValuePairs, DateTimeOffset? absoluteExpiration = null);

// Insert a single object
IObservable<Unit> InsertObject<T>(string key, T value, DateTimeOffset? absoluteExpiration = null);

// Insert a group of objects
IObservable<Unit> InsertObjects<T>(IDictionary<string, T> keyValuePairs, DateTimeOffset? absoluteExpiration = null);

/*
 * Remove items from the store
 */

// Delete a single item
IObservable<Unit> Invalidate(string key);

// Delete a list of items
IObservable<Unit> Invalidate(IEnumerable<string> keys);

// Delete a single object (do *not* use Invalidate for items inserted with InsertObject!)
IObservable<Unit> InvalidateObject<T>(string key);

// Deletes a list of objects
IObservable<Unit> InvalidateObjects<T>(IEnumerable<string> keys);

// Deletes all items (regardless if they are objects or not)
IObservable<Unit> InvalidateAll();

// Deletes all objects of type T
IObservable<Unit> InvalidateAllObjects<T>();

/*
 * Get Metadata about items
 */

// Return a list of all keys. Use for debugging purposes only.
IObservable<IEnumerable<string>> GetAllKeys();

// Return the time which an item was created
IObservable<DateTimeOffset?> GetCreatedAt(string key);

// Return the time which an object of type T was created
IObservable<DateTimeOffset?> GetObjectCreatedAt<T>(string key);

// Return the time which a list of keys were created
IObservable<IDictionary<string, DateTimeOffset?>> GetCreatedAt(IEnumerable<string> keys);

/*
 * Utility methods
 */

// Attempt to ensure all outstanding operations are written to disk
IObservable<Unit> Flush();

// Preemptively drop all expired keys and run SQLite's VACUUM method on the
// underlying database
IObservable<Unit> Vacuum();

Extension Method Documentation

On top of every IBlobCache object, there are extension methods that help with common application scenarios:

/*
 * Username / Login Methods (only available on ISecureBlobCache)
 */

// Save login information for the given host
IObservable<Unit> SaveLogin(string user, string password, string host = "default", DateTimeOffset? absoluteExpiration = null);

// Load information for the given host
IObservable<LoginInfo> GetLoginAsync(string host = "default");

// Erase information for the given host
IObservable<Unit> EraseLogin(string host = "default");

/*
 * Downloading and caching URLs and Images
 */

// Download a file as a byte array
IObservable<byte[]> DownloadUrl(string url,
    IDictionary<string, string> headers = null,
    bool fetchAlways = false,
    DateTimeOffset? absoluteExpiration = null);

// Load a given key as an image
IObservable<IBitmap> LoadImage(string key, float? desiredWidth = null, float? desiredHeight = null);

// Download an image from the network and load it
IObservable<IBitmap> LoadImageFromUrl(string url,
    bool fetchAlways = false,
    float? desiredWidth = null,
    float? desiredHeight = null,
    DateTimeOffset? absoluteExpiration = null);

/*
 * Composite operations
 */

// Attempt to return an object from the cache. If the item doesn't
// exist or returns an error, call a Func to return the latest
// version of an object and insert the result in the cache.
IObservable<T> GetOrFetchObject<T>(string key, Func<Task<T>> fetchFunc, DateTimeOffset? absoluteExpiration = null);

// Like GetOrFetchObject, but isn't async
IObservable<T> GetOrCreateObject<T>(string key, Func<T> fetchFunc, DateTimeOffset? absoluteExpiration = null);

// Immediately return a cached version of an object if available, but *always*
// also execute fetchFunc to retrieve the latest version of an object.
IObservable<T> GetAndFetchLatest<T>(string key,
    Func<Task<T>> fetchFunc,
    Func<DateTimeOffset, bool> fetchPredicate = null,
    DateTimeOffset? absoluteExpiration = null);