| /* | |
| 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); | |
| } | |
| } | |
| } | |