diff --git a/examples/ReadFromUri/ReadFromUri.cs b/examples/ReadFromUri/ReadFromUri.cs index a817af4e3..055659711 100644 --- a/examples/ReadFromUri/ReadFromUri.cs +++ b/examples/ReadFromUri/ReadFromUri.cs @@ -136,6 +136,11 @@ public FileAbstraction (string file) public Stream WriteStream => new FileStream (Name, FileMode.Open); + public bool ReadShareWhenWriting { + get => throw new NotSupportedException (); + set => throw new NotSupportedException (); + } + public void CloseStream (Stream stream) { stream.Close (); diff --git a/src/TaglibSharp.Tests/FileFormats/Id3BothFormatTest.cs b/src/TaglibSharp.Tests/FileFormats/Id3BothFormatTest.cs index adcccc2cc..c155841c5 100644 --- a/src/TaglibSharp.Tests/FileFormats/Id3BothFormatTest.cs +++ b/src/TaglibSharp.Tests/FileFormats/Id3BothFormatTest.cs @@ -1,4 +1,6 @@ using NUnit.Framework; +using System.Threading; +using System.Threading.Tasks; using TagLib; namespace TaglibSharp.Tests.FileFormats @@ -133,5 +135,43 @@ public void TestCreateId3Tags () file = File.Create (tempFile); Assert.AreEqual (TagTypes.Id3v1 | TagTypes.Id3v2, file.TagTypes); } + + [Test] + public async Task TestReadShareWhenWriting () + { + // Simulate having another thread open the file and begin playing it (by + // keeping it open) until we tell it to stop playback and close the file. + SemaphoreSlim playbackStarted = new SemaphoreSlim (0); + SemaphoreSlim stopPlayback = new SemaphoreSlim (0); + _ = Task.Run (async () => { + using (var fileToPlay = System.IO.File.Open (tmp_file, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite)) { + playbackStarted.Release(); + await stopPlayback.WaitAsync(); + } + }); + + // Wait until playback in the other thread has begun. + await playbackStarted.WaitAsync(); + + // Now that the file is in use, ensure writing to it leads to an IOException + // due to the AudioFile's write stream not being shared for reading. + Assert.Catch (typeof (System.IO.IOException), () => { + var file = File.Create (tmp_file); + file.RemoveTags (TagTypes.AllTags); + file.Save (); + }); + + // Now try writing again, but this time explicitly mark the write + // stream as being shared for reading. Ensure no exception occurs. + Assert.DoesNotThrow (() => { + var file = File.Create (tmp_file); + file.FileAbstraction.ReadShareWhenWriting = true; + file.RemoveTags (TagTypes.AllTags); + file.Save (); + }); + + // Finally, tell the other thread it can now "stop" playback and close the file. + stopPlayback.Release(); + } } } diff --git a/src/TaglibSharp.Tests/Helpers.cs b/src/TaglibSharp.Tests/Helpers.cs index f1b989606..0061b9248 100644 --- a/src/TaglibSharp.Tests/Helpers.cs +++ b/src/TaglibSharp.Tests/Helpers.cs @@ -78,6 +78,11 @@ public MemoryFileAbstraction (int maxSize, byte[] data) public Stream WriteStream => stream; + public bool ReadShareWhenWriting { + get => throw new NotSupportedException (); + set => throw new NotSupportedException (); + } + public void CloseStream (Stream stream) { // This causes a stackoverflow diff --git a/src/TaglibSharp/File.cs b/src/TaglibSharp/File.cs index a9b1d8147..e5ea015e5 100644 --- a/src/TaglibSharp/File.cs +++ b/src/TaglibSharp/File.cs @@ -189,6 +189,12 @@ public enum AccessMode /// List corruption_reasons; + /// + /// Specifies whether the file should be readable by other + /// threads while being written to by the thread that opened it. + /// + public bool read_share_when_writing; + #endregion @@ -1621,7 +1627,15 @@ public LocalFileAbstraction (string path) /// public Stream WriteStream => System.IO.File.Open (Name, FileMode.Open, - FileAccess.ReadWrite); + FileAccess.ReadWrite, + ReadShareWhenWriting ? FileShare.Read : FileShare.None); + + /// + /// Gets or sets a value indicating whether the file should + /// be readable by other threads while being written to by + /// the thread that opened it. + /// + public bool ReadShareWhenWriting { get; set; } /// /// Closes a stream created by the current instance. @@ -1793,6 +1807,19 @@ public interface IFileAbstraction /// Stream WriteStream { get; } + /// + /// Gets or sets a value indicating whether the file + /// should be readable by other threads while being + /// written to by the thread that opened it. + /// + /// + /// This property is useful when editing ID3 tags of + /// MP3 files. In particular, setting it to true + /// ensures you can listen to a song in one application + /// while simultaneously editing its ID3 tags in another. + /// + bool ReadShareWhenWriting { get; set; } + /// /// Closes a stream originating from the current /// instance. diff --git a/src/TaglibSharp/Tiff/Rw2/IFDReader.cs b/src/TaglibSharp/Tiff/Rw2/IFDReader.cs index 32cff564b..6e68b8e51 100644 --- a/src/TaglibSharp/Tiff/Rw2/IFDReader.cs +++ b/src/TaglibSharp/Tiff/Rw2/IFDReader.cs @@ -21,6 +21,7 @@ // USA // +using System; using System.IO; using TagLib.IFD; @@ -133,5 +134,10 @@ public void CloseStream (Stream stream) public Stream WriteStream { get { return ReadStream; } } + + public bool ReadShareWhenWriting { + get => throw new NotSupportedException (); + set => throw new NotSupportedException (); + } } }