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
{