diff --git a/WordsLive.Core.Tests/Songs/SongUriTests.cs b/WordsLive.Core.Tests/Songs/SongUriTests.cs new file mode 100644 index 0000000..9ff71f9 --- /dev/null +++ b/WordsLive.Core.Tests/Songs/SongUriTests.cs @@ -0,0 +1,59 @@ +/* + * WordsLive - worship projection software + * Copyright (c) 2014 Patrick Reisert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using WordsLive.Core.Songs.Storage; +using Xunit; + +namespace WordsLive.Core.Tests.Songs +{ + public class SongUriTests + { + public static TheoryData TestData => + new TheoryData + { + { "test.ppl", "song:///test.ppl", false }, + { "test+test.ppl", "song:///test%2Btest.ppl", false }, + { "test&test.ppl", "song:///test%26test.ppl", false }, + { "test (test).ppl", "song:///test%20%28test%29.ppl", false }, + { "test [test].ppl", "song:///test%20%5Btest%5D.ppl", false }, + { "test äöüß.ppl", "song:///test%20%C3%A4%C3%B6%C3%BC%C3%9F.ppl", false }, + { "#test.ppl", "song:///%23test.ppl", false }, + { "subfolder/test.ppl", "song:///subfolder/test.ppl", false }, + { "subfolder\\test.ppl", "song:///subfolder/test.ppl", true }, + }; + + [Theory] + [MemberData(nameof(TestData))] + public void GetUri(string filename, string uri, bool _) + { + Assert.Equal(uri, SongUri.GetUri(filename).OriginalString); + } + + [Theory] + [MemberData(nameof(TestData))] + public void GetFilename(string filename, string uri, bool oneWayOnly) + { + if (oneWayOnly) + { + return; + } + Assert.Equal(filename, SongUri.GetFilename(new Uri(uri))); + } + } +} diff --git a/WordsLive.Core.Tests/WordsLive.Core.Tests.csproj b/WordsLive.Core.Tests/WordsLive.Core.Tests.csproj index 20387c3..603c649 100644 --- a/WordsLive.Core.Tests/WordsLive.Core.Tests.csproj +++ b/WordsLive.Core.Tests/WordsLive.Core.Tests.csproj @@ -51,6 +51,7 @@ + diff --git a/WordsLive.Core/MediaManager.cs b/WordsLive.Core/MediaManager.cs index 3bc4319..bc8603f 100644 --- a/WordsLive.Core/MediaManager.cs +++ b/WordsLive.Core/MediaManager.cs @@ -21,6 +21,7 @@ using System.IO; using System.Linq; using System.Xml.Linq; +using WordsLive.Core.Songs.Storage; namespace WordsLive.Core { @@ -239,7 +240,7 @@ public static IEnumerable LoadPortfolio(string fileName) { foreach (Media m in from i in root.Element("order").Elements("item") select (i.Attribute("mediatype").Value == "powerpraise-song" && !i.Element("file").Value.Contains('\\')) ? - LoadMediaMetadata(new Uri("song:///" + i.Element("file").Value), LoadOptions(i)) : + LoadMediaMetadata(SongUri.GetUri(i.Element("file").Value), LoadOptions(i)) : LoadMediaMetadata(new Uri(i.Element("file").Value), LoadOptions(i))) { yield return m; @@ -250,7 +251,7 @@ public static IEnumerable LoadPortfolio(string fileName) else if (root.Attribute("version").Value == "2.2") { foreach (Media m in from i in root.Elements("item") - select MediaManager.LoadMediaMetadata(new Uri("song:///" + i.Element("file").Value), null)) + select MediaManager.LoadMediaMetadata(SongUri.GetUri(i.Element("file").Value), null)) { yield return m; } @@ -336,7 +337,7 @@ from m in enumerable private static string GetMediaPathFromUri(Uri uri) { if (uri.Scheme == "song") - return Uri.UnescapeDataString(uri.AbsolutePath).Substring(1); + return SongUri.GetFilename(uri); if (uri.IsFile) return uri.LocalPath; diff --git a/WordsLive.Core/Songs/Storage/LocalSongStorage.cs b/WordsLive.Core/Songs/Storage/LocalSongStorage.cs index 8af1c1a..692e207 100644 --- a/WordsLive.Core/Songs/Storage/LocalSongStorage.cs +++ b/WordsLive.Core/Songs/Storage/LocalSongStorage.cs @@ -59,10 +59,11 @@ public override IEnumerable All() try { - var relativePath = new Uri(directory + Path.DirectorySeparatorChar) + var escapedRelativePath = new Uri(directory + Path.DirectorySeparatorChar) .MakeRelativeUri(new Uri(file)) .ToString(); - var song = new Song(new Uri("song:///" + relativePath), new SongUriResolver(this)); + var relativePath = Uri.UnescapeDataString(escapedRelativePath); + var song = new Song(SongUri.GetUri(relativePath), new SongUriResolver(this)); data = SongData.Create(song); } catch { } @@ -162,7 +163,7 @@ public override Uri TryRewriteUri(Uri uri) if (filePath.StartsWith(rootPath, StringComparison.OrdinalIgnoreCase)) { var relativePath = filePath.Substring(rootPath.Length).Replace(Path.DirectorySeparatorChar, '/'); - return new Uri("song:///" + relativePath); + return SongUri.GetUri(relativePath); } } diff --git a/WordsLive.Core/Songs/Storage/SongData.cs b/WordsLive.Core/Songs/Storage/SongData.cs index c70ae6d..1cc332f 100644 --- a/WordsLive.Core/Songs/Storage/SongData.cs +++ b/WordsLive.Core/Songs/Storage/SongData.cs @@ -128,7 +128,7 @@ public Uri Uri { get { - return new Uri("song:///" + Filename); + return SongUri.GetUri(Filename); } } @@ -143,7 +143,7 @@ public static SongData Create(Song song) return new SongData { Title = song.Title, - Filename = Uri.UnescapeDataString(String.Join("", song.Uri.Segments.Skip(1))), + Filename = song.Uri.Scheme == "song" ? SongUri.GetFilename(song.Uri) : null, Text = song.TextWithoutChords, Translation = song.TranslationWithoutChords, Copyright = String.Join(" ", song.Copyright.Split('\n').Select(line => line.Trim())), diff --git a/WordsLive.Core/Songs/Storage/SongUri.cs b/WordsLive.Core/Songs/Storage/SongUri.cs new file mode 100644 index 0000000..b71e102 --- /dev/null +++ b/WordsLive.Core/Songs/Storage/SongUri.cs @@ -0,0 +1,53 @@ +/* + * WordsLive - worship projection software + * Copyright (c) 2013 Patrick Reisert + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +using System; +using System.Linq; + +namespace WordsLive.Core.Songs.Storage +{ + /// + /// Methods for handling "song://" URIs. + /// + public class SongUri + { + /// + /// Returns a "song://" URI for the given filename within the song storage folder. + /// + /// The filename. Include the subfolder when the file is in a subfolder of the song storage folder. + /// The "song://" URI with encoded special characters if needed. + public static Uri GetUri(string filename) + { + var escapedFilename = string.Join("/", filename.Split('/', '\\').Select(Uri.EscapeDataString)); + return new Uri("song:///" + escapedFilename); + } + + /// + /// Extracts the filename within the song storage folder from a "song://" URI. + /// + /// The "song://" URI. + /// The filename relative to the song storage folder. If the file is in a subfolder, then this includes the subfolder and the used delimiter is "/". + public static string GetFilename(Uri uri) + { + if (uri.Scheme != "song") + throw new ArgumentException("uri"); + + return Uri.UnescapeDataString(uri.AbsolutePath).Substring(1); + } + } +} diff --git a/WordsLive.Core/Songs/Storage/SongUriResolver.cs b/WordsLive.Core/Songs/Storage/SongUriResolver.cs index a418f65..f406fe7 100644 --- a/WordsLive.Core/Songs/Storage/SongUriResolver.cs +++ b/WordsLive.Core/Songs/Storage/SongUriResolver.cs @@ -61,7 +61,7 @@ public virtual Stream Get(Uri uri) { if (uri.Scheme == "song") { - return (ForceStorage ?? DataManager.Songs).Get(GetFilename(uri)).Stream; + return (ForceStorage ?? DataManager.Songs).Get(SongUri.GetFilename(uri)).Stream; } else if (uri.IsFile) @@ -85,7 +85,7 @@ public virtual async Task GetAsync(Uri uri, CancellationToken cancellati { if (uri.Scheme == "song") { - var entry = await (ForceStorage ?? DataManager.Songs).GetAsync(GetFilename(uri), cancellation); + var entry = await (ForceStorage ?? DataManager.Songs).GetAsync(SongUri.GetFilename(uri), cancellation); return entry.Stream; } else if (uri.IsFile) @@ -109,7 +109,7 @@ public virtual FileTransaction Put(Uri uri) { if (uri.Scheme == "song") { - return (ForceStorage ?? DataManager.Songs).Put(GetFilename(uri)); + return (ForceStorage ?? DataManager.Songs).Put(SongUri.GetFilename(uri)); } else if (uri.IsFile) { @@ -120,13 +120,5 @@ public virtual FileTransaction Put(Uri uri) throw new NotSupportedException(); } } - - private static string GetFilename(Uri uri) - { - if (uri.Scheme != "song") - throw new ArgumentException("uri"); - - return Uri.UnescapeDataString(uri.AbsolutePath).Substring(1); - } } } diff --git a/WordsLive.Core/WordsLive.Core.csproj b/WordsLive.Core/WordsLive.Core.csproj index 0ddf4b0..70524b5 100644 --- a/WordsLive.Core/WordsLive.Core.csproj +++ b/WordsLive.Core/WordsLive.Core.csproj @@ -82,6 +82,7 @@ + diff --git a/WordsLive/Editor/EditorWindow.xaml.cs b/WordsLive/Editor/EditorWindow.xaml.cs index 16802f4..88541e3 100644 --- a/WordsLive/Editor/EditorWindow.xaml.cs +++ b/WordsLive/Editor/EditorWindow.xaml.cs @@ -212,7 +212,7 @@ private void SaveSongAs(Song song) if (dlg.ShowDialog() == true) { - song.Save(new Uri("song:///" + dlg.Filename)); + song.Save(SongUri.GetUri(dlg.Filename)); } } diff --git a/WordsLive/MediaOrderList/MediaOrderItem.cs b/WordsLive/MediaOrderList/MediaOrderItem.cs index 24c32b9..7d4a1b7 100644 --- a/WordsLive/MediaOrderList/MediaOrderItem.cs +++ b/WordsLive/MediaOrderList/MediaOrderItem.cs @@ -21,6 +21,7 @@ using System.Linq; using System.Windows.Media; using WordsLive.Core; +using WordsLive.Core.Songs.Storage; namespace WordsLive.MediaOrderList { @@ -75,7 +76,7 @@ public string Path } else if (Data.Uri.Scheme == "song") { - return Uri.UnescapeDataString(Data.Uri.AbsolutePath).Substring(1); + return SongUri.GetFilename(Data.Uri); } else {