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