yavsc/Yavsc/GoogleApiSupport/Google.Apis/Util/Store/FileDataStore.cs

184 lines
7.2 KiB
C#

/*
Copyright 2017 Google Inc
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.
*/
// TODO: This does not support UWP Storage.
using Google.Apis.Json;
using System;
using System.IO;
using System.Threading.Tasks;
namespace Google.Apis.Util.Store
{
/// <summary>
/// File data store that implements <see cref="IDataStore"/>. This store creates a different file for each
/// combination of type and key. This file data store stores a JSON format of the specified object.
/// </summary>
public class FileDataStore : IDataStore
{
private const string XdgDataHomeSubdirectory = "google-filedatastore";
private static readonly Task CompletedTask = Task.FromResult(0);
readonly string folderPath;
/// <summary>Gets the full folder path.</summary>
public string FolderPath { get { return folderPath; } }
/// <summary>
/// Constructs a new file data store. If <c>fullPath</c> is <c>false</c> the path will be used as relative to
/// <c>Environment.SpecialFolder.ApplicationData"</c> on Windows, or <c>$HOME</c> on Linux and MacOS,
/// otherwise the input folder will be treated as absolute.
/// The folder is created if it doesn't exist yet.
/// </summary>
/// <param name="folder">Folder path.</param>
/// <param name="fullPath">
/// Defines whether the folder parameter is absolute or relative to
/// <c>Environment.SpecialFolder.ApplicationData</c> on Windows, or<c>$HOME</c> on Linux and MacOS.
/// </param>
public FileDataStore(string folder, bool fullPath = false)
{
folderPath = fullPath
? folder
: Path.Combine(GetHomeDirectory(), folder);
if (!Directory.Exists(folderPath))
{
Directory.CreateDirectory(folderPath);
}
}
private string GetHomeDirectory()
{
string appData = Environment.GetEnvironmentVariable("APPDATA");
if (!string.IsNullOrEmpty(appData))
{
// This is almost certainly windows.
// This path must be the same between the desktop FileDataStore and this netstandard FileDataStore.
return appData;
}
string home = Environment.GetEnvironmentVariable("HOME");
if (!string.IsNullOrEmpty(home))
{
// This is almost certainly Linux or MacOS.
// Follow the XDG Base Directory Specification: https://specifications.freedesktop.org/basedir-spec/latest/index.html
// Store data in subdirectory of $XDG_DATA_HOME if it exists, defaulting to $HOME/.local/share if not set.
string xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
if (string.IsNullOrEmpty(xdgDataHome))
{
xdgDataHome = Path.Combine(home, ".local", "share");
}
return Path.Combine(xdgDataHome, XdgDataHomeSubdirectory);
}
throw new PlatformNotSupportedException("Relative FileDataStore paths not supported on this platform.");
}
/// <summary>
/// Stores the given value for the given key. It creates a new file (named <see cref="GenerateStoredKey"/>) in
/// <see cref="FolderPath"/>.
/// </summary>
/// <typeparam name="T">The type to store in the data store.</typeparam>
/// <param name="key">The key.</param>
/// <param name="value">The value to store in the data store.</param>
public Task StoreAsync<T>(string key, T value)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
var serialized = NewtonsoftJsonSerializer.Instance.Serialize(value);
var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T)));
File.WriteAllText(filePath, serialized);
return CompletedTask;
}
/// <summary>
/// Deletes the given key. It deletes the <see cref="GenerateStoredKey"/> named file in
/// <see cref="FolderPath"/>.
/// </summary>
/// <param name="key">The key to delete from the data store.</param>
public Task DeleteAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T)));
if (File.Exists(filePath))
{
File.Delete(filePath);
}
return CompletedTask;
}
/// <summary>
/// Returns the stored value for the given key or <c>null</c> if the matching file (<see cref="GenerateStoredKey"/>
/// in <see cref="FolderPath"/> doesn't exist.
/// </summary>
/// <typeparam name="T">The type to retrieve.</typeparam>
/// <param name="key">The key to retrieve from the data store.</param>
/// <returns>The stored object.</returns>
public Task<T> GetAsync<T>(string key)
{
if (string.IsNullOrEmpty(key))
{
throw new ArgumentException("Key MUST have a value");
}
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T)));
if (File.Exists(filePath))
{
try
{
var obj = File.ReadAllText(filePath);
tcs.SetResult(NewtonsoftJsonSerializer.Instance.Deserialize<T>(obj));
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}
else
{
tcs.SetResult(default(T));
}
return tcs.Task;
}
/// <summary>
/// Clears all values in the data store. This method deletes all files in <see cref="FolderPath"/>.
/// </summary>
public Task ClearAsync()
{
if (Directory.Exists(folderPath))
{
Directory.Delete(folderPath, true);
Directory.CreateDirectory(folderPath);
}
return CompletedTask;
}
/// <summary>Creates a unique stored key based on the key and the class type.</summary>
/// <param name="key">The object key.</param>
/// <param name="t">The type to store or retrieve.</param>
public static string GenerateStoredKey(string key, Type t)
{
return string.Format("{0}-{1}", t.FullName, key);
}
}
}