/* 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 { /// /// File data store that implements . 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. /// public class FileDataStore : IDataStore { private const string XdgDataHomeSubdirectory = "google-filedatastore"; private static readonly Task CompletedTask = Task.FromResult(0); readonly string folderPath; /// Gets the full folder path. public string FolderPath { get { return folderPath; } } /// /// Constructs a new file data store. If fullPath is false the path will be used as relative to /// Environment.SpecialFolder.ApplicationData" on Windows, or $HOME on Linux and MacOS, /// otherwise the input folder will be treated as absolute. /// The folder is created if it doesn't exist yet. /// /// Folder path. /// /// Defines whether the folder parameter is absolute or relative to /// Environment.SpecialFolder.ApplicationData on Windows, or$HOME on Linux and MacOS. /// 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."); } /// /// Stores the given value for the given key. It creates a new file (named ) in /// . /// /// The type to store in the data store. /// The key. /// The value to store in the data store. public Task StoreAsync(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; } /// /// Deletes the given key. It deletes the named file in /// . /// /// The key to delete from the data store. public Task DeleteAsync(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; } /// /// Returns the stored value for the given key or null if the matching file ( /// in doesn't exist. /// /// The type to retrieve. /// The key to retrieve from the data store. /// The stored object. public Task GetAsync(string key) { if (string.IsNullOrEmpty(key)) { throw new ArgumentException("Key MUST have a value"); } TaskCompletionSource tcs = new TaskCompletionSource(); var filePath = Path.Combine(folderPath, GenerateStoredKey(key, typeof(T))); if (File.Exists(filePath)) { try { var obj = File.ReadAllText(filePath); tcs.SetResult(NewtonsoftJsonSerializer.Instance.Deserialize(obj)); } catch (Exception ex) { tcs.SetException(ex); } } else { tcs.SetResult(default(T)); } return tcs.Task; } /// /// Clears all values in the data store. This method deletes all files in . /// public Task ClearAsync() { if (Directory.Exists(folderPath)) { Directory.Delete(folderPath, true); Directory.CreateDirectory(folderPath); } return CompletedTask; } /// Creates a unique stored key based on the key and the class type. /// The object key. /// The type to store or retrieve. public static string GenerateStoredKey(string key, Type t) { return string.Format("{0}-{1}", t.FullName, key); } } }