/// <summary> /// Reads lines from the current position to the end of the file. /// </summary> private static async Task <CacheRetrievalResult> RetrieveInternalAsync(FileStream fs) { List <Scrobble> result = new List <Scrobble>(); List <string> failedLines = new List <string>(); using (var reader = GetReader(fs)) { while (!reader.EndOfStream) { // Try to parse each line to a scrobble, ignoring failures. var line = await reader.ReadLineAsync(); if (!string.IsNullOrWhiteSpace(line)) { try { var scrobble = ScrobbleSerializer.Deserialize(line); result.Add(scrobble); } catch { failedLines.Add(line); } } } } return(new CacheRetrievalResult(result, failedLines)); }
/// <summary> /// Asynchronously stores a scrobble in the underlying cache file. /// Will throw an exception if the cache file cannot be accessed. /// This method is thread safe. /// </summary> public async Task StoreAsync(Scrobble scrobble) { EnsureFileLockIsAcquired(); using (await _fileOperationAsyncLock.LockAsync()) { // Note about the file content: // The file is line separated. // Instead of writing the data followed by a new line, we do the opposite: // We always start by writing a new line, followed by the data. // This way, we don't risk continuing a corrupted line without a proper line ending. // Unreadable lines can then be discarded, and we lose only one record instead of two. await EnsureCorrectHeaderAndGetFileVersionAsync(_fileStream); // Skip to the end of the file to append a new scrobble. _fileStream.Seek(0, SeekOrigin.End); var serializedScrobble = ScrobbleSerializer.Serialize(scrobble); byte[] buffer = Encoding.UTF8.GetBytes(serializedScrobble); byte[] newLineBuffer = Encoding.UTF8.GetBytes("\n"); await _fileStream.WriteAsync(newLineBuffer, 0, newLineBuffer.Length); await _fileStream.WriteAsync(buffer, 0, buffer.Length); } }
/// <summary> /// Erases the file, write a new header, then write the scrobbles. /// </summary> private static async Task OverwriteFileAsync(FileStream fs, IEnumerable <Scrobble> scrobbles) { // Truncate the file. fs.SetLength(0); // Write the header. await EnsureCorrectHeaderAndGetFileVersionAsync(fs); // Skip to the end of the file to append the scrobbles. fs.Seek(0, SeekOrigin.End); // To minimize file writes, we use a StringBuilder that will be written in the file // in as few buffer lengths as possible. StringBuilder sb = new StringBuilder(); foreach (var scrobble in scrobbles) { var serialized = ScrobbleSerializer.Serialize(scrobble); sb.AppendLine(); sb.Append(serialized); } byte[] buffer = Encoding.UTF8.GetBytes(sb.ToString()); await fs.WriteAsync(buffer, 0, buffer.Length); }