/// <summary> /// Gets a collection from the cache /// </summary> /// <param name="collectionIdentifier"></param> /// <returns></returns> private async Task <IonCollection> getCollectionFromCacheAsync(CollectionCacheIndex cacheIndex, bool serverCallAsBackup) { string collectionURL = PagesURLs.getCollectionURL(_config); // retrieve from memory cache IonCollection collection = _memoryCache.collection; if (collection != null) { return(collection); } // try to load collection from isolated storage try { collection = await StorageUtils.loadCollectionFromIsolatedStorageAsync(_config).ConfigureAwait(false); // Add collection to memory cache if (collection != null) { _memoryCache.collection = collection; } } catch (Exception e) { IonLogging.log("Error getting collection from isolated storage. Message: " + e.Message, IonLogMessageTypes.ERROR); } return(collection); }
/// <summary> /// Used to retrieve a class that inherits from CacheIndex /// </summary> /// <typeparam name="T"></typeparam> /// <param name="requestUrl"></param> /// <param name="collectionIdentifier"></param> /// <returns>First it tries to get a index from memoryCache, then from fileCache and after that returns null, if no index is found</returns> public static async Task <T> retrieve <T>(string requestUrl, IonConfig config) where T : CacheIndex { T index = MemoryCacheIndex.get <T>(requestUrl, config.collectionIdentifier); if (index != null) { IonLogging.log("Index lookup " + requestUrl + " from memory", IonLogMessageTypes.SUCCESS); return(index); } // check isolated storage try { index = await StorageUtils.getIndexAsync <T>(requestUrl, config).ConfigureAwait(false); if (index != null) { IonLogging.log("Index lookup " + requestUrl + " from isolated storage", IonLogMessageTypes.SUCCESS); } } catch (Exception e) { IonLogging.log("Index lookup " + requestUrl + " is not in isolated storage. Message: " + e.Message, IonLogMessageTypes.INFORMATION); } // Save to memory cache, if index is not null if (index != null) { MemoryCacheIndex.put(requestUrl, config.collectionIdentifier, index); } return(index); }
/// <summary> /// Used to get a page with a desired identifier /// </summary> /// <param name="pageIdentifier"></param> /// <returns>Already parsed IonPage</returns> public async Task <IonPage> getPageAsync(string pageIdentifier) { string pageURL = PagesURLs.getPageURL(_config, pageIdentifier); PageCacheIndex pageCacheIndex = await PageCacheIndex.retrieve(pageURL, _config).ConfigureAwait(false); bool isNetworkConnected = NetworkUtils.isOnline(); if (pageCacheIndex == null) { if (isNetworkConnected) { IonLogging.log("Loading page \"" + pageIdentifier + "\" from server.", IonLogMessageTypes.SUCCESS); return(await getPageFromServerAsync(pageIdentifier).ConfigureAwait(false)); } else { IonLogging.log("Error getting page \"" + pageIdentifier + "\" from server or cache.", IonLogMessageTypes.ERROR); throw new PageNotAvailableException(); } } // Get collection IonCollection collection = await getCollectionAsync().ConfigureAwait(false); // Get last changed of the page from collection DateTime pageLastChanged = collection.getPageLastChanged(pageIdentifier); // Estimate, if the page is outdated or not bool isOutdated = pageCacheIndex.isOutdated(pageLastChanged); if (!isOutdated) { IonLogging.log("Loading page \"" + pageIdentifier + "\" from cache.", IonLogMessageTypes.SUCCESS); return(await getPageFromCacheAsync(pageIdentifier).ConfigureAwait(false)); } else { if (isNetworkConnected) { // Download page from server IonLogging.log("Loading newer version of page \"" + pageIdentifier + "\" from server.", IonLogMessageTypes.SUCCESS); return(await getPageFromServerAsync(pageIdentifier).ConfigureAwait(false)); } else { // get old version from cache IonLogging.log("Loading potentially old version of page \"" + pageIdentifier + "\" from cache.", IonLogMessageTypes.WARNING); return(await getPageFromCacheAsync(pageIdentifier).ConfigureAwait(false)); } } }
/// <summary> /// Saves a index to memory and isolated storage cache /// </summary> /// <typeparam name="T"></typeparam> /// <param name="requestURL"></param> /// <param name="cacheIndex"></param> /// <param name="config"></param> public static async Task save <T>(string requestURL, T cacheIndex, IonConfig config) where T : CacheIndex { try { // save to memory cache MemoryCacheIndex.put <T>(requestURL, config.collectionIdentifier, cacheIndex); // save to isolated storage await StorageUtils.saveIndexAsync(requestURL, cacheIndex, config).ConfigureAwait(false); } catch (Exception e) { IonLogging.log("Cache Index " + requestURL + " could not be saved. Message: " + e.Message, IonLogMessageTypes.ERROR); } }
/// <summary> /// Clears the memory and file caches /// </summary> /// <param name="collectionIdentifier"></param> /// <param name="locale"></param> public static async Task clear(string collectionIdentifier) { try { // Clear memory cache MemoryCacheIndex.clear(collectionIdentifier); // Clear isolated storage cache await StorageUtils.deleteFolderInIsolatedStorageAsync(collectionIdentifier).ConfigureAwait(false); } catch (Exception e) { IonLogging.log("Error cleaning caches: " + e.Message, IonLogMessageTypes.ERROR); } }
/// <summary> /// Checks the cache for a possible duplicate and removes it /// </summary> /// <param name="key"></param> private void removePossibleDouplicate(K key) { // Check if there is a existing node with the specified key LinkedListNode <LRUCacheItem <K, V> > node; if (cacheMap.TryGetValue(key, out node)) { // Decrease capacity _capacity -= node.Value.sizeInByte; // Remove node from the lruList lruList.Remove(node); // Remove key from the cacheMap cacheMap.Remove(key); IonLogging.log("Removed duplicate node from LRU-Cache with the key: " + key.ToString(), IonLogMessageTypes.INFORMATION); } }
/// <summary> /// Used to get the collection of the class /// </summary> /// <returns>The collection of this pages</returns> public async Task <IonCollection> getCollectionAsync() { string collectionURL = PagesURLs.getCollectionURL(_config); CollectionCacheIndex cacheIndex = await CollectionCacheIndex.retrieve(collectionURL, _config).ConfigureAwait(false); // Check if there is a not outdated cacheIndex avialible bool currentCacheEntry = cacheIndex != null && !cacheIndex.isOutdated(_config); bool networkConnected = NetworkUtils.isOnline(); if (currentCacheEntry) { // retrieve current version from cache IonLogging.log("Loading collection \"" + _config.collectionIdentifier + "\" from cache.", IonLogMessageTypes.SUCCESS); return(await getCollectionFromCacheAsync(cacheIndex, false).ConfigureAwait(false)); } else { if (networkConnected) { // download collection or check for modifications IonLogging.log("Loading collection \"" + _config.collectionIdentifier + "\" from server.", IonLogMessageTypes.SUCCESS); return(await getCollectionFromServerAsync(cacheIndex, false).ConfigureAwait(false)); } else { if (cacheIndex != null) { // no network: use potential old version from cache IonLogging.log("Using potentially old version of collection \"" + _config.collectionIdentifier + "\" from cache, because there is no internet connection.", IonLogMessageTypes.WARNING); return(await getCollectionFromCacheAsync(cacheIndex, false).ConfigureAwait(false)); } else { // Collection can neither be downloaded nor be found in cache IonLogging.log("Couldn't get collection \"" + _config.collectionIdentifier + "\" either from server or cache.", IonLogMessageTypes.ERROR); throw new CollectionNotAvailableException(); } } } }
// login to get access token public static async Task <AuthenticationHeaderValue> GetAuthHeaderValue(string username, string password, string loginAdress) { // Temporary httpClient for the login process HttpClient loginClient = new HttpClient(); // Login data object Login loginData; // Generate POST request Dictionary <string, string> request = new Dictionary <string, string>(); request.Add("username", username); request.Add("password", password); FormUrlEncodedContent requestContent = new FormUrlEncodedContent(request); try { // Post request to server HttpResponseMessage response = await loginClient.PostAsync(new Uri( loginAdress ), requestContent).ConfigureAwait(false); // Process and return the acces token string jsonResult = response.Content.ReadAsStringAsync().Result; // Generate the model and try to parse the answer from the server LoginRootObject lro = new LoginRootObject(); loginData = JsonConvert.DeserializeObject <LoginRootObject>(jsonResult).login; // Create the http authentication header return(new AuthenticationHeaderValue("Token", loginData.token)); } catch (Exception e) { IonLogging.log("Error logging in: " + e.Message, IonLogMessageTypes.ERROR); return(null); } }
/// <summary> /// Gets a page directly from the server /// </summary> /// <param name="pageIdentifier"></param> /// <returns></returns> private async Task <IonPage> getPageFromServerAsync(string pageIdentifier) { try { // Retrieve the page from the server HttpResponseMessage response = await _dataClient.getPageAsync(pageIdentifier).ConfigureAwait(false); IonPage page = await DataParser.parsePageAsync(response).ConfigureAwait(false); // Add page to cache, if it is not null if (page != null) { await savePageToCachesAsync(page, _config); } return(page); } catch (Exception e) { IonLogging.log("Error getting page " + pageIdentifier + " from server! " + e.Message, IonLogMessageTypes.ERROR); return(null); } }
/// <summary> /// Parses a given pageString to a IonPage /// </summary> /// <param name="pageString"></param> /// <returns>Parsed IonPage</returns> public static IonPage parsePage(string pageString) { // Parse the page to a raw page container IonPage pageParsed = new IonPage(); try { // Deserialize page IonPageRoot pageParsedNew = JsonConvert.DeserializeObject <IonPageRoot>(pageString); // Set the first element of the root to be the page pageParsed = pageParsedNew.page[0]; // Sort out empty content pageParsed.sortOutEmptyContent(); } catch (Exception e) { IonLogging.log("Error deserializing page json: " + e.Message, IonLogMessageTypes.ERROR); } return(pageParsed); }
/// <summary> /// Gets a collection from the server /// </summary> /// <param name="collectionIdentifier"></param> /// <returns></returns> private async Task <IonCollection> getCollectionFromServerAsync(CollectionCacheIndex cacheIndex, bool cacheAsBackup) { //DateTime lastModified = cacheIndex != null ? cacheIndex.lastModified : DateTime.MinValue; try { // Retrive collecion from server and parse it HttpResponseMessage response = await _dataClient.getCollectionAsync(_config.collectionIdentifier, cacheIndex != null?cacheIndex.lastModified : DateTime.MinValue).ConfigureAwait(false); // Only parse the answer if it is not newer than the cached version if (!(response.StatusCode == System.Net.HttpStatusCode.NotModified)) { // Parse collection IonCollection collection = await DataParser.parseCollectionAsync(response).ConfigureAwait(false); // Add collection to memory cache _memoryCache.collection = collection; // Save collection to isolated storage await StorageUtils.saveCollectionToIsolatedStorageAsync(collection, _config).ConfigureAwait(false); // save cacheIndex await saveCollectionCacheIndexAsync(collection.last_changed).ConfigureAwait(false); return(collection); } else { // Collection in the server is the same as stored already in isolated storage cache if (_memoryCache.collection == null) { // Only load collection from isolated storage cache, if the memory cache has no collection cached try { // Get collection from isolated storage IonCollection collection = await StorageUtils.loadCollectionFromIsolatedStorageAsync(_config).ConfigureAwait(false); // Add collection to memory cache if (collection != null) { _memoryCache.collection = collection; } // change the last-mofied date in the cacheIndex to now await saveCollectionCacheIndexAsync(collection.last_changed).ConfigureAwait(false); return(collection); } catch (Exception e) { IonLogging.log("Error getting collection from isolated storage. Message: " + e.Message, IonLogMessageTypes.ERROR); return(null); } } else { // change the last-mofied date in the cacheIndex to now await saveCollectionCacheIndexAsync(_memoryCache.collection.last_changed).ConfigureAwait(false); return(_memoryCache.collection); } } } catch (Exception e) { IonLogging.log("Error retreiving collection data: " + e.Message, IonLogMessageTypes.ERROR); return(null); } }
/// <summary> /// Used to load and extract an archive to the caches /// </summary> /// <param name="ionFiles"></param> /// <param name="ionPages"></param> /// <param name="url"></param> /// <param name="callback"></param> /// <returns></returns> public async Task loadArchiveAsync(IIonFiles ionFiles, IIonPages ionPages, string url, Action callback = null) { // Temporary used elements in the isolated storage StorageFile archiveFile = null; StorageFolder tempFolder = null; // Get temp-folder path string tempFolderPath = FilePaths.getTempFolderPath(_config); // Lock all used elements using (await _fileLocks.ObtainLock(url).LockAsync().ConfigureAwait(false)) using (await _fileLocks.ObtainLock(tempFolderPath).LockAsync().ConfigureAwait(false)) { try { // Request archive file archiveFile = await ionFiles.requestArchiveFileAsync(url); // Get tempFolder for extraction tempFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(tempFolderPath, CreationCollisionOption.ReplaceExisting); // Generate fileStream from archiveFile using (Stream stream = (Stream)await archiveFile.OpenStreamForReadAsync()) { // Extract file to tempFolder await TarUtils.ExtractTarArchiveAsync(stream, tempFolder).ConfigureAwait(false); } // Get all elements listed in the index file string indexFileString = await FileIO.ReadTextAsync(await tempFolder.GetFileAsync("index.json")); List <ArchiveElement> elementsList = JsonConvert.DeserializeObject <List <ArchiveElement> >(indexFileString); // Handle each element of the index.json for (int i = 0; i < elementsList.Count; i++) { IonRequestInfo requestInfo = PagesURLs.analyze(elementsList[i].url, _config); // Treat every element regarding its type switch (requestInfo.requestType) { case IonRequestType.MEDIA: { // Get all the needed folder- and file names StorageFolder mediaFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync(FilePaths.getMediaFolderPath(_config, false), CreationCollisionOption.OpenIfExists); string sourcePath = tempFolder.Path + IonConstants.BackSlash + elementsList[i].name.Replace('/', (char)IonConstants.BackSlash[0]); string destinationPath = mediaFolder.Path + IonConstants.BackSlash + FilePaths.getFileName(elementsList[i].url); // Delete a possible existing file if (File.Exists(destinationPath)) { File.Delete(destinationPath); } // Move the file from the temp to the media folder File.Move(sourcePath, destinationPath); // Create index for file FileCacheIndex index = new FileCacheIndex(elementsList[i].url, elementsList[i].checksum, DateTimeUtils.now()); // Save file index await CacheIndexStore.save(elementsList[i].url, index, _config).ConfigureAwait(false); break; } case IonRequestType.PAGE: { // Extract the page json from the file string pageString = await FileIO.ReadTextAsync(await tempFolder.GetFileAsync(elementsList[i].name.Replace('/', (char)IonConstants.BackSlash[0]))); // Parse the new page IonPage page = DataParser.parsePage(pageString); // Save the page to the caches await ionPages.savePageToCachesAsync(page, _config).ConfigureAwait(false); break; } default: { IonLogging.log("Object " + elementsList[i].url + " could not be parsed from archive.", IonLogMessageTypes.ERROR); break; } } } } catch (Exception e) { IonLogging.log("Error at the archive download: " + e.Message, IonLogMessageTypes.ERROR); } finally { // Clean up all temperary used elements if (archiveFile != null) { // Delete index file string indexFilePath = FilePaths.getCacheIndicesFolderPath(_config) + IonConstants.BackSlash + archiveFile.Name + IonConstants.JsonFileExtension; if (File.Exists(indexFilePath)) { File.Delete(indexFilePath); } // Delete archive file await archiveFile.DeleteAsync(); } if (tempFolder != null) { await tempFolder.DeleteAsync(); } // Call the callback action if set before if (callback != null) { callback(); } } } // Release the file locks _fileLocks.ReleaseLock(url); _fileLocks.ReleaseLock(tempFolderPath); }