diff --git a/.copr/Makefile b/.copr/Makefile
deleted file mode 100644
index ba330ada9..000000000
--- a/.copr/Makefile
+++ /dev/null
@@ -1,59 +0,0 @@
-VERSION := $(shell sed -ne '/^Version:/s/.* *//p' \
- deployment/fedora-package-x64/pkg-src/jellyfin.spec)
-
-deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz:
- curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
- https://github.com/jellyfin/jellyfin-web/archive/v$(VERSION).tar.gz \
- || curl -f -L -o deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz \
- https://github.com/jellyfin/jellyfin-web/archive/master.tar.gz \
-
-srpm: deployment/fedora-package-x64/pkg-src/jellyfin-web-$(VERSION).tar.gz
- cd deployment/fedora-package-x64; \
- SOURCE_DIR=../.. \
- WORKDIR="$${PWD}"; \
- package_temporary_dir="$${WORKDIR}/pkg-dist-tmp"; \
- pkg_src_dir="$${WORKDIR}/pkg-src"; \
- GNU_TAR=1; \
- tar \
- --transform "s,^\.,jellyfin-$(VERSION)," \
- --exclude='.git*' \
- --exclude='**/.git' \
- --exclude='**/.hg' \
- --exclude='**/.vs' \
- --exclude='**/.vscode' \
- --exclude='deployment' \
- --exclude='**/bin' \
- --exclude='**/obj' \
- --exclude='**/.nuget' \
- --exclude='*.deb' \
- --exclude='*.rpm' \
- -czf "pkg-src/jellyfin-$(VERSION).tar.gz" \
- -C $${SOURCE_DIR} ./ || GNU_TAR=0; \
- if [ $${GNU_TAR} -eq 0 ]; then \
- package_temporary_dir="$$(mktemp -d)"; \
- mkdir -p "$${package_temporary_dir}/jellyfin"; \
- tar \
- --exclude='.git*' \
- --exclude='**/.git' \
- --exclude='**/.hg' \
- --exclude='**/.vs' \
- --exclude='**/.vscode' \
- --exclude='deployment' \
- --exclude='**/bin' \
- --exclude='**/obj' \
- --exclude='**/.nuget' \
- --exclude='*.deb' \
- --exclude='*.rpm' \
- -czf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
- -C $${SOURCE_DIR} ./; \
- mkdir -p "$${package_temporary_dir}/jellyfin-$(VERSION)"; \
- tar -xzf "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz" \
- -C "$${package_temporary_dir}/jellyfin-$(VERSION); \
- rm -f "$${package_temporary_dir}/jellyfin/jellyfin-$(VERSION).tar.gz"; \
- tar -czf "$${SOURCE_DIR}/SOURCES/pkg-src/jellyfin-$(VERSION).tar.gz" \
- -C "$${package_temporary_dir}" "jellyfin-$(VERSION); \
- rm -rf $${package_temporary_dir}; \
- fi; \
- rpmbuild -bs pkg-src/jellyfin.spec \
- --define "_sourcedir $$PWD/pkg-src/" \
- --define "_srcrpmdir $(outdir)"
diff --git a/.copr/Makefile b/.copr/Makefile
new file mode 120000
index 000000000..ec3c90dfd
--- /dev/null
+++ b/.copr/Makefile
@@ -0,0 +1 @@
+../fedora/Makefile
\ No newline at end of file
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..e902dc712
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,3 @@
+# Joshua must review all changes to deployment and build.sh
+deployment/* @joshuaboniface
+build.sh @joshuaboniface
diff --git a/.gitignore b/.gitignore
index 5ce0145db..53b7f1ff0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -246,14 +246,14 @@ pip-log.txt
#########################
# Artifacts for debian-x64
-deployment/debian-package-x64/pkg-src/.debhelper/
-deployment/debian-package-x64/pkg-src/*.debhelper
-deployment/debian-package-x64/pkg-src/debhelper-build-stamp
-deployment/debian-package-x64/pkg-src/files
-deployment/debian-package-x64/pkg-src/jellyfin.substvars
-deployment/debian-package-x64/pkg-src/jellyfin/
+debian/.debhelper/
+debian/*.debhelper
+debian/debhelper-build-stamp
+debian/files
+debian/jellyfin.substvars
+debian/jellyfin/
# Don't ignore the debian/bin folder
-!deployment/debian-package-x64/pkg-src/bin/
+!debian/bin/
deployment/**/dist/
deployment/**/pkg-dist/
@@ -273,3 +273,8 @@ dist
# BenchmarkDotNet artifacts
BenchmarkDotNet.Artifacts
+
+# Ignore web artifacts from native builds
+web/
+web-src.*
+MediaBrowser.WebDashboard/jellyfin-web/
diff --git a/Emby.Dlna/Didl/DidlBuilder.cs b/Emby.Dlna/Didl/DidlBuilder.cs
index 59951f6d9..f7d840c62 100644
--- a/Emby.Dlna/Didl/DidlBuilder.cs
+++ b/Emby.Dlna/Didl/DidlBuilder.cs
@@ -425,57 +425,99 @@ namespace Emby.Dlna.Didl
}
}
- if (item is Episode episode && context is Season season)
+ return item is Episode episode
+ ? GetEpisodeDisplayName(episode, context)
+ : item.Name;
+ }
+
+ ///
+ /// Gets episode display name appropriate for the given context.
+ ///
+ ///
+ /// If context is a season, this will return a string containing just episode number and name.
+ /// Otherwise the result will include series nams and season number.
+ ///
+ /// The episode.
+ /// Current context.
+ /// Formatted name of the episode.
+ private string GetEpisodeDisplayName(Episode episode, BaseItem context)
+ {
+ string[] components;
+
+ if (context is Season season)
{
// This is a special embedded within a season
- if (item.ParentIndexNumber.HasValue && item.ParentIndexNumber.Value == 0
+ if (episode.ParentIndexNumber.HasValue && episode.ParentIndexNumber.Value == 0
&& season.IndexNumber.HasValue && season.IndexNumber.Value != 0)
{
return string.Format(
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("ValueSpecialEpisodeName"),
- item.Name);
+ episode.Name);
}
- if (item.IndexNumber.HasValue)
- {
- var number = item.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
-
- if (episode.IndexNumberEnd.HasValue)
- {
- number += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
- }
-
- return number + " - " + item.Name;
- }
+ // inside a season use simple format (ex. '12 - Episode Name')
+ var epNumberName = GetEpisodeIndexFullName(episode);
+ components = new[] { epNumberName, episode.Name };
}
- else if (item is Episode ep)
+ else
{
- var parent = ep.GetParent();
- var name = parent.Name + " - ";
-
- if (ep.ParentIndexNumber.HasValue)
- {
- name += "S" + ep.ParentIndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
- }
- else if (!item.IndexNumber.HasValue)
- {
- return name + " - " + item.Name;
- }
-
- name += "E" + ep.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
- if (ep.IndexNumberEnd.HasValue)
- {
- name += "-" + ep.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
- }
-
- name += " - " + item.Name;
- return name;
+ // outside a season include series and season details (ex. 'TV Show - S05E11 - Episode Name')
+ var epNumberName = GetEpisodeNumberDisplayName(episode);
+ components = new[] { episode.SeriesName, epNumberName, episode.Name };
}
- return item.Name;
+ return string.Join(" - ", components.Where(NotNullOrWhiteSpace));
}
+ ///
+ /// Gets complete episode number.
+ ///
+ /// The episode.
+ /// For single episodes returns just the number. For double episodes - current and ending numbers.
+ private string GetEpisodeIndexFullName(Episode episode)
+ {
+ var name = string.Empty;
+ if (episode.IndexNumber.HasValue)
+ {
+ name += episode.IndexNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+
+ if (episode.IndexNumberEnd.HasValue)
+ {
+ name += "-" + episode.IndexNumberEnd.Value.ToString("00", CultureInfo.InvariantCulture);
+ }
+ }
+
+ return name;
+ }
+
+ ///
+ /// Gets episode number formatted as 'S##E##'.
+ ///
+ /// The episode.
+ /// Formatted episode number.
+ private string GetEpisodeNumberDisplayName(Episode episode)
+ {
+ var name = string.Empty;
+ var seasonNumber = episode.Season?.IndexNumber;
+
+ if (seasonNumber.HasValue)
+ {
+ name = "S" + seasonNumber.Value.ToString("00", CultureInfo.InvariantCulture);
+ }
+
+ var indexName = GetEpisodeIndexFullName(episode);
+
+ if (!string.IsNullOrWhiteSpace(indexName))
+ {
+ name += "E" + indexName;
+ }
+
+ return name;
+ }
+
+ private bool NotNullOrWhiteSpace(string s) => !string.IsNullOrWhiteSpace(s);
+
private void AddAudioResource(XmlWriter writer, BaseItem audio, string deviceId, Filter filter, StreamInfo streamInfo = null)
{
writer.WriteStartElement(string.Empty, "res", NS_DIDL);
diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs
index d10804b22..2347ebd0d 100644
--- a/Emby.Dlna/Profiles/DefaultProfile.cs
+++ b/Emby.Dlna/Profiles/DefaultProfile.cs
@@ -12,7 +12,7 @@ namespace Emby.Dlna.Profiles
{
Name = "Generic Device";
- ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*";
+ ProtocolInfo = "http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*";
Manufacturer = "Jellyfin";
ModelDescription = "UPnP/AV 1.0 Compliant Media Server";
diff --git a/Emby.Dlna/Profiles/Xml/Default.xml b/Emby.Dlna/Profiles/Xml/Default.xml
index daac4135a..9460f9d5a 100644
--- a/Emby.Dlna/Profiles/Xml/Default.xml
+++ b/Emby.Dlna/Profiles/Xml/Default.xml
@@ -21,7 +21,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Denon AVR.xml b/Emby.Dlna/Profiles/Xml/Denon AVR.xml
index c76cd9a89..571786906 100644
--- a/Emby.Dlna/Profiles/Xml/Denon AVR.xml
+++ b/Emby.Dlna/Profiles/Xml/Denon AVR.xml
@@ -26,7 +26,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
index f2ce68ab5..eea0febfd 100644
--- a/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
+++ b/Emby.Dlna/Profiles/Xml/DirecTV HD-DVR.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
10
true
true
diff --git a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
index a0f0e0ee8..20f5ba79b 100644
--- a/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/LG Smart TV.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
10
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
index 55910c77f..d01e3a145 100644
--- a/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
+++ b/Emby.Dlna/Profiles/Xml/Linksys DMA2100.xml
@@ -25,7 +25,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Marantz.xml b/Emby.Dlna/Profiles/Xml/Marantz.xml
index a6345ab3f..0cc9c86e8 100644
--- a/Emby.Dlna/Profiles/Xml/Marantz.xml
+++ b/Emby.Dlna/Profiles/Xml/Marantz.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
index 2c2c3a1de..9d5ddc3d1 100644
--- a/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
+++ b/Emby.Dlna/Profiles/Xml/MediaMonkey.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
index 934f0550d..8f766853b 100644
--- a/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
+++ b/Emby.Dlna/Profiles/Xml/Panasonic Viera.xml
@@ -28,7 +28,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
10
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
index eab220fae..aa881d014 100644
--- a/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
+++ b/Emby.Dlna/Profiles/Xml/Popcorn Hour.xml
@@ -21,7 +21,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
index 3e6f56e5b..7160a9c2e 100644
--- a/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/Samsung Smart TV.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
index 74240b843..c9b907e58 100644
--- a/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
+++ b/Emby.Dlna/Profiles/Xml/Sharp Smart TV.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
true
true
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
index 49819ccfd..e516ff512 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2011).xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
index aaad7b342..88bd1c2f5 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2012).xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
index 8e2e8dbaa..3ca9893cd 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2013).xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
index 17a6135e1..8804a75df 100644
--- a/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
+++ b/Emby.Dlna/Profiles/Xml/Sony Bravia (2014).xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
index df385135c..bafa44b82 100644
--- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
+++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 3.xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
index 20f50f6b6..eb8e645b3 100644
--- a/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
+++ b/Emby.Dlna/Profiles/Xml/Sony PlayStation 4.xml
@@ -29,7 +29,7 @@
192000
10
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Dlna/Profiles/Xml/WDTV Live.xml b/Emby.Dlna/Profiles/Xml/WDTV Live.xml
index 05380e33a..ccb74ee64 100644
--- a/Emby.Dlna/Profiles/Xml/WDTV Live.xml
+++ b/Emby.Dlna/Profiles/Xml/WDTV Live.xml
@@ -28,7 +28,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
5
false
false
diff --git a/Emby.Dlna/Profiles/Xml/Xbox One.xml b/Emby.Dlna/Profiles/Xml/Xbox One.xml
index 5f5cf1af3..26a65bbd4 100644
--- a/Emby.Dlna/Profiles/Xml/Xbox One.xml
+++ b/Emby.Dlna/Profiles/Xml/Xbox One.xml
@@ -28,7 +28,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
40
false
false
diff --git a/Emby.Dlna/Profiles/Xml/foobar2000.xml b/Emby.Dlna/Profiles/Xml/foobar2000.xml
index f3eedb35c..5ce75ace5 100644
--- a/Emby.Dlna/Profiles/Xml/foobar2000.xml
+++ b/Emby.Dlna/Profiles/Xml/foobar2000.xml
@@ -27,7 +27,7 @@
140000000
192000
- http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*image/jpeg:*,http-get:*image/png:*,http-get:*image/gif:*,http-get:*image/tiff:*
+ http-get:*:video/mpeg:*,http-get:*:video/mp4:*,http-get:*:video/vnd.dlna.mpeg-tts:*,http-get:*:video/avi:*,http-get:*:video/x-matroska:*,http-get:*:video/x-ms-wmv:*,http-get:*:video/wtv:*,http-get:*:audio/mpeg:*,http-get:*:audio/mp3:*,http-get:*:audio/mp4:*,http-get:*:audio/x-ms-wma:*,http-get:*:audio/wav:*,http-get:*:audio/L16:*,http-get:*:image/jpeg:*,http-get:*:image/png:*,http-get:*:image/gif:*,http-get:*:image/tiff:*
0
false
false
diff --git a/Emby.Naming/Audio/AlbumParser.cs b/Emby.Naming/Audio/AlbumParser.cs
index 33f4468d9..b63be3a64 100644
--- a/Emby.Naming/Audio/AlbumParser.cs
+++ b/Emby.Naming/Audio/AlbumParser.cs
@@ -1,9 +1,9 @@
+#nullable enable
#pragma warning disable CS1591
using System;
using System.Globalization;
using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Common;
@@ -21,8 +21,7 @@ namespace Emby.Naming.Audio
public bool IsMultiPart(string path)
{
var filename = Path.GetFileName(path);
-
- if (string.IsNullOrEmpty(filename))
+ if (filename.Length == 0)
{
return false;
}
@@ -39,18 +38,22 @@ namespace Emby.Naming.Audio
filename = filename.Replace(')', ' ');
filename = Regex.Replace(filename, @"\s+", " ");
- filename = filename.TrimStart();
+ ReadOnlySpan trimmedFilename = filename.TrimStart();
foreach (var prefix in _options.AlbumStackingPrefixes)
{
- if (filename.IndexOf(prefix, StringComparison.OrdinalIgnoreCase) != 0)
+ if (!trimmedFilename.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{
continue;
}
- var tmp = filename.Substring(prefix.Length);
+ var tmp = trimmedFilename.Slice(prefix.Length).Trim();
- tmp = tmp.Trim().Split(' ').FirstOrDefault() ?? string.Empty;
+ int index = tmp.IndexOf(' ');
+ if (index != -1)
+ {
+ tmp = tmp.Slice(0, index);
+ }
if (int.TryParse(tmp, NumberStyles.Integer, CultureInfo.InvariantCulture, out _))
{
diff --git a/Emby.Naming/Audio/AudioFileParser.cs b/Emby.Naming/Audio/AudioFileParser.cs
index 25d5f8735..6b2f4be93 100644
--- a/Emby.Naming/Audio/AudioFileParser.cs
+++ b/Emby.Naming/Audio/AudioFileParser.cs
@@ -1,3 +1,4 @@
+#nullable enable
#pragma warning disable CS1591
using System;
@@ -11,7 +12,7 @@ namespace Emby.Naming.Audio
{
public static bool IsAudioFile(string path, NamingOptions options)
{
- var extension = Path.GetExtension(path) ?? string.Empty;
+ var extension = Path.GetExtension(path);
return options.AudioFileExtensions.Contains(extension, StringComparer.OrdinalIgnoreCase);
}
}
diff --git a/Emby.Naming/Common/EpisodeExpression.cs b/Emby.Naming/Common/EpisodeExpression.cs
index 07de72851..ed6ba8881 100644
--- a/Emby.Naming/Common/EpisodeExpression.cs
+++ b/Emby.Naming/Common/EpisodeExpression.cs
@@ -23,11 +23,6 @@ namespace Emby.Naming.Common
{
}
- public EpisodeExpression()
- : this(null)
- {
- }
-
public string Expression
{
get => _expression;
@@ -48,6 +43,6 @@ namespace Emby.Naming.Common
public string[] DateTimeFormats { get; set; }
- public Regex Regex => _regex ?? (_regex = new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled));
+ public Regex Regex => _regex ??= new Regex(Expression, RegexOptions.IgnoreCase | RegexOptions.Compiled);
}
}
diff --git a/Emby.Naming/Subtitles/SubtitleParser.cs b/Emby.Naming/Subtitles/SubtitleParser.cs
index 88ec3e2d6..24e59f90a 100644
--- a/Emby.Naming/Subtitles/SubtitleParser.cs
+++ b/Emby.Naming/Subtitles/SubtitleParser.cs
@@ -1,3 +1,4 @@
+#nullable enable
#pragma warning disable CS1591
using System;
@@ -16,11 +17,11 @@ namespace Emby.Naming.Subtitles
_options = options;
}
- public SubtitleInfo ParseFile(string path)
+ public SubtitleInfo? ParseFile(string path)
{
- if (string.IsNullOrEmpty(path))
+ if (path.Length == 0)
{
- throw new ArgumentNullException(nameof(path));
+ throw new ArgumentException("File path can't be empty.", nameof(path));
}
var extension = Path.GetExtension(path);
@@ -52,11 +53,6 @@ namespace Emby.Naming.Subtitles
private string[] GetFlags(string path)
{
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
-
// Note: the tags need be be surrounded be either a space ( ), hyphen -, dot . or underscore _.
var file = Path.GetFileName(path);
diff --git a/Emby.Notifications/NotificationEntryPoint.cs b/Emby.Notifications/NotificationEntryPoint.cs
index befecc570..869b7407e 100644
--- a/Emby.Notifications/NotificationEntryPoint.cs
+++ b/Emby.Notifications/NotificationEntryPoint.cs
@@ -143,7 +143,7 @@ namespace Emby.Notifications
var notification = new NotificationRequest
{
- Description = "Please see jellyfin.media for details.",
+ Description = "Please see jellyfin.org for details.",
NotificationType = type,
Name = _localization.GetLocalizedString("NewVersionIsAvailable")
};
diff --git a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
index 0925d72a6..4685a03ac 100644
--- a/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
+++ b/Emby.Server.Implementations/Activity/ActivityLogEntryPoint.cs
@@ -137,7 +137,7 @@ namespace Emby.Server.Implementations.Activity
CultureInfo.InvariantCulture,
_localization.GetLocalizedString("SubtitleDownloadFailureFromForItem"),
e.Provider,
- Emby.Notifications.NotificationEntryPoint.GetItemName(e.Item)),
+ Notifications.NotificationEntryPoint.GetItemName(e.Item)),
Type = "SubtitleDownloadFailure",
ItemId = e.Item.Id.ToString("N", CultureInfo.InvariantCulture),
ShortOverview = e.Exception.Message
@@ -265,31 +265,20 @@ namespace Emby.Server.Implementations.Activity
private void OnSessionEnded(object sender, SessionEventArgs e)
{
- string name;
var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName))
{
- name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("DeviceOfflineWithName"),
- session.DeviceName);
-
- // Causing too much spam for now
return;
}
- else
- {
- name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("UserOfflineFromDevice"),
- session.UserName,
- session.DeviceName);
- }
CreateLogEntry(new ActivityLogEntry
{
- Name = name,
+ Name = string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("UserOfflineFromDevice"),
+ session.UserName,
+ session.DeviceName),
Type = "SessionEnded",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -388,31 +377,20 @@ namespace Emby.Server.Implementations.Activity
private void OnSessionStarted(object sender, SessionEventArgs e)
{
- string name;
var session = e.SessionInfo;
if (string.IsNullOrEmpty(session.UserName))
{
- name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("DeviceOnlineWithName"),
- session.DeviceName);
-
- // Causing too much spam for now
return;
}
- else
- {
- name = string.Format(
- CultureInfo.InvariantCulture,
- _localization.GetLocalizedString("UserOnlineFromDevice"),
- session.UserName,
- session.DeviceName);
- }
CreateLogEntry(new ActivityLogEntry
{
- Name = name,
+ Name = string.Format(
+ CultureInfo.InvariantCulture,
+ _localization.GetLocalizedString("UserOnlineFromDevice"),
+ session.UserName,
+ session.DeviceName),
Type = "SessionStarted",
ShortOverview = string.Format(
CultureInfo.InvariantCulture,
@@ -580,7 +558,7 @@ namespace Emby.Server.Implementations.Activity
{
int years = days / DaysInYear;
values.Add(CreateValueString(years, "year"));
- days = days % DaysInYear;
+ days %= DaysInYear;
}
// Number of months
@@ -588,7 +566,7 @@ namespace Emby.Server.Implementations.Activity
{
int months = days / DaysInMonth;
values.Add(CreateValueString(months, "month"));
- days = days % DaysInMonth;
+ days %= DaysInMonth;
}
// Number of days
diff --git a/Emby.Server.Implementations/Activity/ActivityManager.cs b/Emby.Server.Implementations/Activity/ActivityManager.cs
index c1d8dd8da..81bebae3d 100644
--- a/Emby.Server.Implementations/Activity/ActivityManager.cs
+++ b/Emby.Server.Implementations/Activity/ActivityManager.cs
@@ -1,25 +1,31 @@
-#pragma warning disable CS1591
-
using System;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Activity;
using MediaBrowser.Model.Events;
using MediaBrowser.Model.Querying;
-using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Activity
{
+ ///
+ /// The activity log manager.
+ ///
public class ActivityManager : IActivityManager
{
private readonly IActivityRepository _repo;
private readonly IUserManager _userManager;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The activity repository.
+ /// The user manager.
public ActivityManager(IActivityRepository repo, IUserManager userManager)
{
_repo = repo;
_userManager = userManager;
}
+ ///
public event EventHandler> EntryCreated;
public void Create(ActivityLogEntry entry)
@@ -31,6 +37,7 @@ namespace Emby.Server.Implementations.Activity
EntryCreated?.Invoke(this, new GenericEventArgs(entry));
}
+ ///
public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var result = _repo.GetActivityLogEntries(minDate, hasUserId, startIndex, limit);
@@ -54,6 +61,7 @@ namespace Emby.Server.Implementations.Activity
return result;
}
+ ///
public QueryResult GetActivityLogEntries(DateTime? minDate, int? startIndex, int? limit)
{
return GetActivityLogEntries(minDate, null, startIndex, limit);
diff --git a/Emby.Server.Implementations/Activity/ActivityRepository.cs b/Emby.Server.Implementations/Activity/ActivityRepository.cs
index 26fc229f7..22796ba3f 100644
--- a/Emby.Server.Implementations/Activity/ActivityRepository.cs
+++ b/Emby.Server.Implementations/Activity/ActivityRepository.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -15,12 +13,21 @@ using SQLitePCL.pretty;
namespace Emby.Server.Implementations.Activity
{
+ ///
+ /// The activity log repository.
+ ///
public class ActivityRepository : BaseSqliteRepository, IActivityRepository
{
private const string BaseActivitySelectText = "select Id, Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity from ActivityLog";
private readonly IFileSystem _fileSystem;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The logger.
+ /// The server application paths.
+ /// The filesystem.
public ActivityRepository(ILogger logger, IServerApplicationPaths appPaths, IFileSystem fileSystem)
: base(logger)
{
@@ -28,6 +35,9 @@ namespace Emby.Server.Implementations.Activity
_fileSystem = fileSystem;
}
+ ///
+ /// Initializes the .
+ ///
public void Initialize()
{
try
@@ -46,16 +56,14 @@ namespace Emby.Server.Implementations.Activity
private void InitializeInternal()
{
- using (var connection = GetConnection())
+ using var connection = GetConnection();
+ connection.RunQueries(new[]
{
- connection.RunQueries(new[]
- {
- "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
- "drop index if exists idx_ActivityLogEntries"
- });
+ "create table if not exists ActivityLog (Id INTEGER PRIMARY KEY, Name TEXT NOT NULL, Overview TEXT, ShortOverview TEXT, Type TEXT NOT NULL, ItemId TEXT, UserId TEXT, DateCreated DATETIME NOT NULL, LogSeverity TEXT NOT NULL)",
+ "drop index if exists idx_ActivityLogEntries"
+ });
- TryMigrate(connection);
- }
+ TryMigrate(connection);
}
private void TryMigrate(ManagedConnection connection)
@@ -77,6 +85,7 @@ namespace Emby.Server.Implementations.Activity
}
}
+ ///
public void Create(ActivityLogEntry entry)
{
if (entry == null)
@@ -84,39 +93,38 @@ namespace Emby.Server.Implementations.Activity
throw new ArgumentNullException(nameof(entry));
}
- using (var connection = GetConnection())
+ using var connection = GetConnection();
+ connection.RunInTransaction(db =>
{
- connection.RunInTransaction(
- db =>
- {
- using (var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)"))
- {
- statement.TryBind("@Name", entry.Name);
+ using var statement = db.PrepareStatement("insert into ActivityLog (Name, Overview, ShortOverview, Type, ItemId, UserId, DateCreated, LogSeverity) values (@Name, @Overview, @ShortOverview, @Type, @ItemId, @UserId, @DateCreated, @LogSeverity)");
+ statement.TryBind("@Name", entry.Name);
- statement.TryBind("@Overview", entry.Overview);
- statement.TryBind("@ShortOverview", entry.ShortOverview);
- statement.TryBind("@Type", entry.Type);
- statement.TryBind("@ItemId", entry.ItemId);
+ statement.TryBind("@Overview", entry.Overview);
+ statement.TryBind("@ShortOverview", entry.ShortOverview);
+ statement.TryBind("@Type", entry.Type);
+ statement.TryBind("@ItemId", entry.ItemId);
- if (entry.UserId.Equals(Guid.Empty))
- {
- statement.TryBindNull("@UserId");
- }
- else
- {
- statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
+ if (entry.UserId.Equals(Guid.Empty))
+ {
+ statement.TryBindNull("@UserId");
+ }
+ else
+ {
+ statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
+ }
- statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
- statement.TryBind("@LogSeverity", entry.Severity.ToString());
+ statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
+ statement.TryBind("@LogSeverity", entry.Severity.ToString());
- statement.MoveNext();
- }
- },
- TransactionMode);
- }
+ statement.MoveNext();
+ }, TransactionMode);
}
+ ///
+ /// Adds the provided to this repository.
+ ///
+ /// The activity log entry.
+ /// If entry is null.
public void Update(ActivityLogEntry entry)
{
if (entry == null)
@@ -124,40 +132,35 @@ namespace Emby.Server.Implementations.Activity
throw new ArgumentNullException(nameof(entry));
}
- using (var connection = GetConnection())
+ using var connection = GetConnection();
+ connection.RunInTransaction(db =>
{
- connection.RunInTransaction(
- db =>
- {
- using (var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id"))
- {
- statement.TryBind("@Id", entry.Id);
+ using var statement = db.PrepareStatement("Update ActivityLog set Name=@Name,Overview=@Overview,ShortOverview=@ShortOverview,Type=@Type,ItemId=@ItemId,UserId=@UserId,DateCreated=@DateCreated,LogSeverity=@LogSeverity where Id=@Id");
+ statement.TryBind("@Id", entry.Id);
- statement.TryBind("@Name", entry.Name);
- statement.TryBind("@Overview", entry.Overview);
- statement.TryBind("@ShortOverview", entry.ShortOverview);
- statement.TryBind("@Type", entry.Type);
- statement.TryBind("@ItemId", entry.ItemId);
+ statement.TryBind("@Name", entry.Name);
+ statement.TryBind("@Overview", entry.Overview);
+ statement.TryBind("@ShortOverview", entry.ShortOverview);
+ statement.TryBind("@Type", entry.Type);
+ statement.TryBind("@ItemId", entry.ItemId);
- if (entry.UserId.Equals(Guid.Empty))
- {
- statement.TryBindNull("@UserId");
- }
- else
- {
- statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
- }
+ if (entry.UserId.Equals(Guid.Empty))
+ {
+ statement.TryBindNull("@UserId");
+ }
+ else
+ {
+ statement.TryBind("@UserId", entry.UserId.ToString("N", CultureInfo.InvariantCulture));
+ }
- statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
- statement.TryBind("@LogSeverity", entry.Severity.ToString());
+ statement.TryBind("@DateCreated", entry.Date.ToDateTimeParamValue());
+ statement.TryBind("@LogSeverity", entry.Severity.ToString());
- statement.MoveNext();
- }
- },
- TransactionMode);
- }
+ statement.MoveNext();
+ }, TransactionMode);
}
+ ///
public QueryResult GetActivityLogEntries(DateTime? minDate, bool? hasUserId, int? startIndex, int? limit)
{
var commandText = BaseActivitySelectText;
@@ -170,14 +173,7 @@ namespace Emby.Server.Implementations.Activity
if (hasUserId.HasValue)
{
- if (hasUserId.Value)
- {
- whereClauses.Add("UserId not null");
- }
- else
- {
- whereClauses.Add("UserId is null");
- }
+ whereClauses.Add(hasUserId.Value ? "UserId not null" : "UserId is null");
}
var whereTextWithoutPaging = whereClauses.Count == 0 ?
@@ -220,38 +216,33 @@ namespace Emby.Server.Implementations.Activity
var list = new List();
var result = new QueryResult();
- using (var connection = GetConnection(true))
- {
- connection.RunInTransaction(
- db =>
+ using var connection = GetConnection(true);
+ connection.RunInTransaction(
+ db =>
+ {
+ var statements = PrepareAll(db, statementTexts).ToList();
+
+ using (var statement = statements[0])
{
- var statements = PrepareAll(db, statementTexts).ToList();
-
- using (var statement = statements[0])
+ if (minDate.HasValue)
{
- if (minDate.HasValue)
- {
- statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
- }
-
- foreach (var row in statement.ExecuteQuery())
- {
- list.Add(GetEntry(row));
- }
+ statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
- using (var statement = statements[1])
- {
- if (minDate.HasValue)
- {
- statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
- }
+ list.AddRange(statement.ExecuteQuery().Select(GetEntry));
+ }
- result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ using (var statement = statements[1])
+ {
+ if (minDate.HasValue)
+ {
+ statement.TryBind("@DateCreated", minDate.Value.ToDateTimeParamValue());
}
- },
- ReadTransactionMode);
- }
+
+ result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ }
+ },
+ ReadTransactionMode);
result.Items = list;
return result;
diff --git a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
index bc4781743..2adc1d6c3 100644
--- a/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
+++ b/Emby.Server.Implementations/AppBase/BaseApplicationPaths.cs
@@ -15,6 +15,11 @@ namespace Emby.Server.Implementations.AppBase
///
/// Initializes a new instance of the class.
///
+ /// The program data path.
+ /// The log directory path.
+ /// The configuration directory path.
+ /// The cache directory path.
+ /// The web directory path.
protected BaseApplicationPaths(
string programDataPath,
string logDirectoryPath,
diff --git a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
index 854d7b4cb..0b681fddf 100644
--- a/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
+++ b/Emby.Server.Implementations/AppBase/ConfigurationHelper.cs
@@ -36,24 +36,22 @@ namespace Emby.Server.Implementations.AppBase
configuration = Activator.CreateInstance(type);
}
- using (var stream = new MemoryStream())
+ using var stream = new MemoryStream();
+ xmlSerializer.SerializeToStream(configuration, stream);
+
+ // Take the object we just got and serialize it back to bytes
+ var newBytes = stream.ToArray();
+
+ // If the file didn't exist before, or if something has changed, re-save
+ if (buffer == null || !buffer.SequenceEqual(newBytes))
{
- xmlSerializer.SerializeToStream(configuration, stream);
+ Directory.CreateDirectory(Path.GetDirectoryName(path));
- // Take the object we just got and serialize it back to bytes
- var newBytes = stream.ToArray();
-
- // If the file didn't exist before, or if something has changed, re-save
- if (buffer == null || !buffer.SequenceEqual(newBytes))
- {
- Directory.CreateDirectory(Path.GetDirectoryName(path));
-
- // Save it after load in case we got new items
- File.WriteAllBytes(path, newBytes);
- }
-
- return configuration;
+ // Save it after load in case we got new items
+ File.WriteAllBytes(path, newBytes);
}
+
+ return configuration;
}
}
}
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 33aec1a06..ffc916b98 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -106,6 +106,7 @@ using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OperatingSystem = MediaBrowser.Common.System.OperatingSystem;
+using Prometheus.DotNetRuntime;
namespace Emby.Server.Implementations
{
@@ -259,6 +260,12 @@ namespace Emby.Server.Implementations
_startupOptions = options;
+ // Initialize runtime stat collection
+ if (ServerConfigurationManager.Configuration.EnableMetrics)
+ {
+ DotNetRuntimeStatsBuilder.Default().StartCollecting();
+ }
+
fileSystem.AddShortcutHandler(new MbLinkShortcutHandler(fileSystem));
_networkManager.NetworkChanged += OnNetworkChanged;
@@ -1185,7 +1192,7 @@ namespace Emby.Server.Implementations
public bool SupportsHttps => Certificate != null || ServerConfigurationManager.Configuration.IsBehindProxy;
- public async Task GetLocalApiUrl(CancellationToken cancellationToken)
+ public async Task GetLocalApiUrl(CancellationToken cancellationToken, bool forceHttp = false)
{
try
{
@@ -1194,7 +1201,7 @@ namespace Emby.Server.Implementations
foreach (var address in addresses)
{
- return GetLocalApiUrl(address);
+ return GetLocalApiUrl(address, forceHttp);
}
return null;
@@ -1224,7 +1231,7 @@ namespace Emby.Server.Implementations
}
///
- public string GetLocalApiUrl(IPAddress ipAddress)
+ public string GetLocalApiUrl(IPAddress ipAddress, bool forceHttp = false)
{
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
{
@@ -1234,20 +1241,21 @@ namespace Emby.Server.Implementations
str.CopyTo(span.Slice(1));
span[^1] = ']';
- return GetLocalApiUrl(span);
+ return GetLocalApiUrl(span, forceHttp);
}
- return GetLocalApiUrl(ipAddress.ToString());
+ return GetLocalApiUrl(ipAddress.ToString(), forceHttp);
}
///
- public string GetLocalApiUrl(ReadOnlySpan host)
+ public string GetLocalApiUrl(ReadOnlySpan host, bool forceHttp = false)
{
var url = new StringBuilder(64);
- url.Append(EnableHttps ? "https://" : "http://")
+ bool useHttps = EnableHttps && !forceHttp;
+ url.Append(useHttps ? "https://" : "http://")
.Append(host)
.Append(':')
- .Append(EnableHttps ? HttpsPort : HttpPort);
+ .Append(useHttps ? HttpsPort : HttpPort);
string baseUrl = ServerConfigurationManager.Configuration.BaseUrl;
if (baseUrl.Length != 0)
diff --git a/Emby.Server.Implementations/Archiving/ZipClient.cs b/Emby.Server.Implementations/Archiving/ZipClient.cs
index e01495e19..591ae547d 100644
--- a/Emby.Server.Implementations/Archiving/ZipClient.cs
+++ b/Emby.Server.Implementations/Archiving/ZipClient.cs
@@ -22,10 +22,8 @@ namespace Emby.Server.Implementations.Archiving
/// if set to true [overwrite existing files].
public void ExtractAll(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
- using (var fileStream = File.OpenRead(sourceFile))
- {
- ExtractAll(fileStream, targetPath, overwriteExistingFiles);
- }
+ using var fileStream = File.OpenRead(sourceFile);
+ ExtractAll(fileStream, targetPath, overwriteExistingFiles);
}
///
@@ -36,70 +34,61 @@ namespace Emby.Server.Implementations.Archiving
/// if set to true [overwrite existing files].
public void ExtractAll(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var reader = ReaderFactory.Open(source))
+ using var reader = ReaderFactory.Open(source);
+ var options = new ExtractionOptions
{
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
+ ExtractFullPath = true
+ };
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
-
- reader.WriteAllToDirectory(targetPath, options);
+ if (overwriteExistingFiles)
+ {
+ options.Overwrite = true;
}
+
+ reader.WriteAllToDirectory(targetPath, options);
}
///
public void ExtractAllFromZip(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var reader = ZipReader.Open(source))
+ using var reader = ZipReader.Open(source);
+ var options = new ExtractionOptions
{
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
+ ExtractFullPath = true,
+ Overwrite = overwriteExistingFiles
+ };
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
-
- reader.WriteAllToDirectory(targetPath, options);
- }
+ reader.WriteAllToDirectory(targetPath, options);
}
///
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var reader = GZipReader.Open(source))
+ using var reader = GZipReader.Open(source);
+ var options = new ExtractionOptions
{
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
+ ExtractFullPath = true,
+ Overwrite = overwriteExistingFiles
+ };
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
-
- reader.WriteAllToDirectory(targetPath, options);
- }
+ reader.WriteAllToDirectory(targetPath, options);
}
///
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
{
- using (var reader = GZipReader.Open(source))
+ using var reader = GZipReader.Open(source);
+ if (reader.MoveToNextEntry())
{
- if (reader.MoveToNextEntry())
- {
- var entry = reader.Entry;
+ var entry = reader.Entry;
- var filename = entry.Key;
- if (string.IsNullOrWhiteSpace(filename))
- {
- filename = defaultFileName;
- }
- reader.WriteEntryToFile(Path.Combine(targetPath, filename));
+ var filename = entry.Key;
+ if (string.IsNullOrWhiteSpace(filename))
+ {
+ filename = defaultFileName;
}
+
+ reader.WriteEntryToFile(Path.Combine(targetPath, filename));
}
}
@@ -111,10 +100,8 @@ namespace Emby.Server.Implementations.Archiving
/// if set to true [overwrite existing files].
public void ExtractAllFrom7z(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
- using (var fileStream = File.OpenRead(sourceFile))
- {
- ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
- }
+ using var fileStream = File.OpenRead(sourceFile);
+ ExtractAllFrom7z(fileStream, targetPath, overwriteExistingFiles);
}
///
@@ -125,21 +112,15 @@ namespace Emby.Server.Implementations.Archiving
/// if set to true [overwrite existing files].
public void ExtractAllFrom7z(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var archive = SevenZipArchive.Open(source))
+ using var archive = SevenZipArchive.Open(source);
+ using var reader = archive.ExtractAllEntries();
+ var options = new ExtractionOptions
{
- using (var reader = archive.ExtractAllEntries())
- {
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
+ ExtractFullPath = true,
+ Overwrite = overwriteExistingFiles
+ };
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
-
- reader.WriteAllToDirectory(targetPath, options);
- }
- }
+ reader.WriteAllToDirectory(targetPath, options);
}
///
@@ -150,10 +131,8 @@ namespace Emby.Server.Implementations.Archiving
/// if set to true [overwrite existing files].
public void ExtractAllFromTar(string sourceFile, string targetPath, bool overwriteExistingFiles)
{
- using (var fileStream = File.OpenRead(sourceFile))
- {
- ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
- }
+ using var fileStream = File.OpenRead(sourceFile);
+ ExtractAllFromTar(fileStream, targetPath, overwriteExistingFiles);
}
///
@@ -164,21 +143,15 @@ namespace Emby.Server.Implementations.Archiving
/// if set to true [overwrite existing files].
public void ExtractAllFromTar(Stream source, string targetPath, bool overwriteExistingFiles)
{
- using (var archive = TarArchive.Open(source))
+ using var archive = TarArchive.Open(source);
+ using var reader = archive.ExtractAllEntries();
+ var options = new ExtractionOptions
{
- using (var reader = archive.ExtractAllEntries())
- {
- var options = new ExtractionOptions();
- options.ExtractFullPath = true;
+ ExtractFullPath = true,
+ Overwrite = overwriteExistingFiles
+ };
- if (overwriteExistingFiles)
- {
- options.Overwrite = true;
- }
-
- reader.WriteAllToDirectory(targetPath, options);
- }
- }
+ reader.WriteAllToDirectory(targetPath, options);
}
}
}
diff --git a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
index 93000ae12..7ae26bd8b 100644
--- a/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
+++ b/Emby.Server.Implementations/Branding/BrandingConfigurationFactory.cs
@@ -1,13 +1,15 @@
-#pragma warning disable CS1591
-
using System.Collections.Generic;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Model.Branding;
namespace Emby.Server.Implementations.Branding
{
+ ///
+ /// A configuration factory for .
+ ///
public class BrandingConfigurationFactory : IConfigurationFactory
{
+ ///
public IEnumerable GetConfigurations()
{
return new[]
diff --git a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
index 6016fed07..3e149cc82 100644
--- a/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelDynamicMediaSourceProvider.cs
@@ -1,7 +1,6 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Channels;
@@ -11,6 +10,9 @@ using MediaBrowser.Model.Dto;
namespace Emby.Server.Implementations.Channels
{
+ ///
+ /// A media source provider for channels.
+ ///
public class ChannelDynamicMediaSourceProvider : IMediaSourceProvider
{
private readonly ChannelManager _channelManager;
@@ -27,12 +29,9 @@ namespace Emby.Server.Implementations.Channels
///
public Task> GetMediaSources(BaseItem item, CancellationToken cancellationToken)
{
- if (item.SourceType == SourceType.Channel)
- {
- return _channelManager.GetDynamicMediaSources(item, cancellationToken);
- }
-
- return Task.FromResult>(new List());
+ return item.SourceType == SourceType.Channel
+ ? _channelManager.GetDynamicMediaSources(item, cancellationToken)
+ : Task.FromResult(Enumerable.Empty());
}
///
diff --git a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
index cf56a5fae..25cbfcf14 100644
--- a/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
+++ b/Emby.Server.Implementations/Channels/ChannelImageProvider.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -11,22 +9,32 @@ using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Channels
{
+ ///
+ /// An image provider for channels.
+ ///
public class ChannelImageProvider : IDynamicImageProvider, IHasItemChangeMonitor
{
private readonly IChannelManager _channelManager;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The channel manager.
public ChannelImageProvider(IChannelManager channelManager)
{
_channelManager = channelManager;
}
+ ///
public string Name => "Channel Image Provider";
+ ///
public IEnumerable GetSupportedImages(BaseItem item)
{
return GetChannel(item).GetSupportedChannelImages();
}
+ ///
public Task GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
{
var channel = GetChannel(item);
@@ -34,6 +42,7 @@ namespace Emby.Server.Implementations.Channels
return channel.GetChannelImage(type, cancellationToken);
}
+ ///
public bool Supports(BaseItem item)
{
return item is Channel;
@@ -46,6 +55,7 @@ namespace Emby.Server.Implementations.Channels
return ((ChannelManager)_channelManager).GetChannelProvider(channel);
}
+ ///
public bool HasChanged(BaseItem item, IDirectoryService directoryService)
{
return GetSupportedImages(item).Any(i => !item.HasImage(i));
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 8502af97a..138832fb8 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -29,6 +27,9 @@ using Microsoft.Extensions.Logging;
namespace Emby.Server.Implementations.Channels
{
+ ///
+ /// The LiveTV channel manager.
+ ///
public class ChannelManager : IChannelManager
{
private readonly IUserManager _userManager;
@@ -45,7 +46,19 @@ namespace Emby.Server.Implementations.Channels
new ConcurrentDictionary>>();
private readonly SemaphoreSlim _resourcePool = new SemaphoreSlim(1, 1);
-
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The user manager.
+ /// The dto service.
+ /// The library manager.
+ /// The logger factory.
+ /// The server configuration manager.
+ /// The filesystem.
+ /// The user data manager.
+ /// The JSON serializer.
+ /// The provider manager.
public ChannelManager(
IUserManager userManager,
IDtoService dtoService,
@@ -72,11 +85,13 @@ namespace Emby.Server.Implementations.Channels
private static TimeSpan CacheLength => TimeSpan.FromHours(3);
+ ///
public void AddParts(IEnumerable channels)
{
Channels = channels.ToArray();
}
+ ///
public bool EnableMediaSourceDisplay(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -85,6 +100,7 @@ namespace Emby.Server.Implementations.Channels
return !(channel is IDisableMediaSourceDisplay);
}
+ ///
public bool CanDelete(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -93,6 +109,7 @@ namespace Emby.Server.Implementations.Channels
return channel is ISupportsDelete supportsDelete && supportsDelete.CanDelete(item);
}
+ ///
public bool EnableMediaProbe(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -101,6 +118,7 @@ namespace Emby.Server.Implementations.Channels
return channel is ISupportsMediaProbe;
}
+ ///
public Task DeleteItem(BaseItem item)
{
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
@@ -127,11 +145,16 @@ namespace Emby.Server.Implementations.Channels
.OrderBy(i => i.Name);
}
+ ///
+ /// Get the installed channel IDs.
+ ///
+ /// An containing installed channel IDs.
public IEnumerable GetInstalledChannelIds()
{
return GetAllChannels().Select(i => GetInternalChannelId(i.Name));
}
+ ///
public QueryResult GetChannelsInternal(ChannelQuery query)
{
var user = query.UserId.Equals(Guid.Empty)
@@ -249,6 +272,7 @@ namespace Emby.Server.Implementations.Channels
};
}
+ ///
public QueryResult GetChannels(ChannelQuery query)
{
var user = query.UserId.Equals(Guid.Empty)
@@ -271,6 +295,12 @@ namespace Emby.Server.Implementations.Channels
return result;
}
+ ///
+ /// Refreshes the associated channels.
+ ///
+ /// The progress.
+ /// A cancellation token that can be used to cancel the operation.
+ /// The completed task.
public async Task RefreshChannels(IProgress progress, CancellationToken cancellationToken)
{
var allChannelsList = GetAllChannels().ToList();
@@ -304,14 +334,7 @@ namespace Emby.Server.Implementations.Channels
private Channel GetChannelEntity(IChannel channel)
{
- var item = GetChannel(GetInternalChannelId(channel.Name));
-
- if (item == null)
- {
- item = GetChannel(channel, CancellationToken.None).Result;
- }
-
- return item;
+ return GetChannel(GetInternalChannelId(channel.Name)) ?? GetChannel(channel, CancellationToken.None).Result;
}
private List GetSavedMediaSources(BaseItem item)
@@ -350,6 +373,7 @@ namespace Emby.Server.Implementations.Channels
_jsonSerializer.SerializeToFile(mediaSources, path);
}
+ ///
public IEnumerable GetStaticMediaSources(BaseItem item, CancellationToken cancellationToken)
{
IEnumerable results = GetSavedMediaSources(item);
@@ -359,6 +383,12 @@ namespace Emby.Server.Implementations.Channels
.ToList();
}
+ ///
+ /// Gets the dynamic media sources based on the provided item.
+ ///
+ /// The item.
+ /// A cancellation token that can be used to cancel the operation.
+ /// The task representing the operation to get the media sources.
public async Task> GetDynamicMediaSources(BaseItem item, CancellationToken cancellationToken)
{
var channel = GetChannel(item.ChannelId);
@@ -403,7 +433,7 @@ namespace Emby.Server.Implementations.Channels
private static MediaSourceInfo NormalizeMediaSource(BaseItem item, MediaSourceInfo info)
{
- info.RunTimeTicks = info.RunTimeTicks ?? item.RunTimeTicks;
+ info.RunTimeTicks ??= item.RunTimeTicks;
return info;
}
@@ -481,31 +511,33 @@ namespace Emby.Server.Implementations.Channels
private static string GetOfficialRating(ChannelParentalRating rating)
{
- switch (rating)
+ return rating switch
{
- case ChannelParentalRating.Adult:
- return "XXX";
- case ChannelParentalRating.UsR:
- return "R";
- case ChannelParentalRating.UsPG13:
- return "PG-13";
- case ChannelParentalRating.UsPG:
- return "PG";
- default:
- return null;
- }
+ ChannelParentalRating.Adult => "XXX",
+ ChannelParentalRating.UsR => "R",
+ ChannelParentalRating.UsPG13 => "PG-13",
+ ChannelParentalRating.UsPG => "PG",
+ _ => null
+ };
}
+ ///
+ /// Gets a channel with the provided Guid.
+ ///
+ /// The Guid.
+ /// The corresponding channel.
public Channel GetChannel(Guid id)
{
return _libraryManager.GetItemById(id) as Channel;
}
+ ///
public Channel GetChannel(string id)
{
return _libraryManager.GetItemById(id) as Channel;
}
+ ///
public ChannelFeatures[] GetAllChannelFeatures()
{
return _libraryManager.GetItemIds(
@@ -516,6 +548,7 @@ namespace Emby.Server.Implementations.Channels
}).Select(i => GetChannelFeatures(i.ToString("N", CultureInfo.InvariantCulture))).ToArray();
}
+ ///
public ChannelFeatures GetChannelFeatures(string id)
{
if (string.IsNullOrEmpty(id))
@@ -529,6 +562,11 @@ namespace Emby.Server.Implementations.Channels
return GetChannelFeaturesDto(channel, channelProvider, channelProvider.GetChannelFeatures());
}
+ ///
+ /// Checks whether the provided Guid supports external transfer.
+ ///
+ /// The Guid.
+ /// Whether or not the provided Guid supports external transfer.
public bool SupportsExternalTransfer(Guid channelId)
{
var channelProvider = GetChannelProvider(channelId);
@@ -536,6 +574,13 @@ namespace Emby.Server.Implementations.Channels
return channelProvider.GetChannelFeatures().SupportsContentDownloading;
}
+ ///
+ /// Gets the provided channel's supported features.
+ ///
+ /// The channel.
+ /// The provider.
+ /// The features.
+ /// The supported features.
public ChannelFeatures GetChannelFeaturesDto(
Channel channel,
IChannel provider,
@@ -570,6 +615,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetNewItemId("Channel " + name, typeof(Channel));
}
+ ///
public async Task> GetLatestChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
{
var internalResult = await GetLatestChannelItemsInternal(query, cancellationToken).ConfigureAwait(false);
@@ -588,6 +634,7 @@ namespace Emby.Server.Implementations.Channels
return result;
}
+ ///
public async Task> GetLatestChannelItemsInternal(InternalItemsQuery query, CancellationToken cancellationToken)
{
var channels = GetAllChannels().Where(i => i is ISupportsLatestMedia).ToArray();
@@ -666,6 +713,7 @@ namespace Emby.Server.Implementations.Channels
}
}
+ ///
public async Task> GetChannelItemsInternal(InternalItemsQuery query, IProgress progress, CancellationToken cancellationToken)
{
// Get the internal channel entity
@@ -727,6 +775,7 @@ namespace Emby.Server.Implementations.Channels
return _libraryManager.GetItemsResult(query);
}
+ ///
public async Task> GetChannelItems(InternalItemsQuery query, CancellationToken cancellationToken)
{
var internalResult = await GetChannelItemsInternal(query, new SimpleProgress(), cancellationToken).ConfigureAwait(false);
@@ -796,7 +845,7 @@ namespace Emby.Server.Implementations.Channels
var query = new InternalChannelItemQuery
{
- UserId = user == null ? Guid.Empty : user.Id,
+ UserId = user?.Id ?? Guid.Empty,
SortBy = sortField,
SortDescending = sortDescending,
FolderId = externalFolderId
@@ -923,60 +972,32 @@ namespace Emby.Server.Implementations.Channels
if (info.Type == ChannelItemType.Folder)
{
- if (info.FolderType == ChannelFolderType.MusicAlbum)
+ item = info.FolderType switch
{
- item = GetItemById(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.FolderType == ChannelFolderType.MusicArtist)
- {
- item = GetItemById(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.FolderType == ChannelFolderType.PhotoAlbum)
- {
- item = GetItemById(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.FolderType == ChannelFolderType.Series)
- {
- item = GetItemById(info.Id, channelProvider.Name, out isNew);
- }
- else if (info.FolderType == ChannelFolderType.Season)
- {
- item = GetItemById(info.Id, channelProvider.Name, out isNew);
- }
- else
- {
- item = GetItemById(info.Id, channelProvider.Name, out isNew);
- }
+ ChannelFolderType.MusicAlbum => GetItemById(info.Id, channelProvider.Name, out isNew),
+ ChannelFolderType.MusicArtist => GetItemById(info.Id, channelProvider.Name, out isNew),
+ ChannelFolderType.PhotoAlbum => GetItemById(info.Id, channelProvider.Name, out isNew),
+ ChannelFolderType.Series => GetItemById(info.Id, channelProvider.Name, out isNew),
+ ChannelFolderType.Season => GetItemById(info.Id, channelProvider.Name, out isNew),
+ _ => GetItemById(info.Id, channelProvider.Name, out isNew)
+ };
}
else if (info.MediaType == ChannelMediaType.Audio)
{
- if (info.ContentType == ChannelMediaContentType.Podcast)
- {
- item = GetItemById(info.Id, channelProvider.Name, out isNew);
- }
- else
- {
- item = GetItemById
public class ServerApplicationPaths : BaseApplicationPaths, IServerApplicationPaths
{
- private string _internalMetadataPath;
-
///
/// Initializes a new instance of the class.
///
@@ -27,6 +25,7 @@ namespace Emby.Server.Implementations
cacheDirectoryPath,
webDirectoryPath)
{
+ InternalMetadataPath = DefaultInternalMetadataPath;
}
///
@@ -98,12 +97,11 @@ namespace Emby.Server.Implementations
/// The user configuration directory path.
public string UserConfigurationDirectoryPath => Path.Combine(ConfigurationDirectoryPath, "users");
+ ///
+ public string DefaultInternalMetadataPath => Path.Combine(ProgramDataPath, "metadata");
+
///
- public string InternalMetadataPath
- {
- get => _internalMetadataPath ?? (_internalMetadataPath = Path.Combine(DataPath, "metadata"));
- set => _internalMetadataPath = value;
- }
+ public string InternalMetadataPath { get; set; }
///
public string VirtualInternalMetadataPath { get; } = "%MetadataPath%";
diff --git a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
index 23e22afd5..56e23d549 100644
--- a/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
+++ b/Emby.Server.Implementations/Services/StringMapTypeDeserializer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Reflection;
+using MediaBrowser.Common.Extensions;
namespace Emby.Server.Implementations.Services
{
@@ -81,7 +82,7 @@ namespace Emby.Server.Implementations.Services
if (propertySerializerEntry.PropertyType == typeof(bool))
{
//InputExtensions.cs#530 MVC Checkbox helper emits extra hidden input field, generating 2 values, first is the real value
- propertyTextValue = LeftPart(propertyTextValue, ',');
+ propertyTextValue = StringExtensions.LeftPart(propertyTextValue, ',').ToString();
}
var value = propertySerializerEntry.PropertyParseStringFn(propertyTextValue);
@@ -95,19 +96,6 @@ namespace Emby.Server.Implementations.Services
return instance;
}
-
- public static string LeftPart(string strVal, char needle)
- {
- if (strVal == null)
- {
- return null;
- }
-
- var pos = strVal.IndexOf(needle);
- return pos == -1
- ? strVal
- : strVal.Substring(0, pos);
- }
}
internal static class TypeAccessor
diff --git a/Emby.Server.Implementations/Services/UrlExtensions.cs b/Emby.Server.Implementations/Services/UrlExtensions.cs
index 5d4407f3b..483c63ade 100644
--- a/Emby.Server.Implementations/Services/UrlExtensions.cs
+++ b/Emby.Server.Implementations/Services/UrlExtensions.cs
@@ -1,4 +1,5 @@
using System;
+using MediaBrowser.Common.Extensions;
namespace Emby.Server.Implementations.Services
{
@@ -13,25 +14,12 @@ namespace Emby.Server.Implementations.Services
public static string GetMethodName(this Type type)
{
var typeName = type.FullName != null // can be null, e.g. generic types
- ? LeftPart(type.FullName, "[[") // Generic Fullname
- .Replace(type.Namespace + ".", string.Empty) // Trim Namespaces
- .Replace("+", ".") // Convert nested into normal type
+ ? StringExtensions.LeftPart(type.FullName, "[[", StringComparison.Ordinal).ToString() // Generic Fullname
+ .Replace(type.Namespace + ".", string.Empty, StringComparison.Ordinal) // Trim Namespaces
+ .Replace("+", ".", StringComparison.Ordinal) // Convert nested into normal type
: type.Name;
return type.IsGenericParameter ? "'" + typeName : typeName;
}
-
- private static string LeftPart(string strVal, string needle)
- {
- if (strVal == null)
- {
- return null;
- }
-
- var pos = strVal.IndexOf(needle, StringComparison.OrdinalIgnoreCase);
- return pos == -1
- ? strVal
- : strVal.Substring(0, pos);
- }
}
}
diff --git a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
index f27f7eeb8..ee5131c1f 100644
--- a/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
+++ b/Emby.Server.Implementations/SocketSharp/WebSocketSharpRequest.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Mime;
+using MediaBrowser.Common.Extensions;
using MediaBrowser.Common.Net;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
@@ -222,14 +223,14 @@ namespace Emby.Server.Implementations.SocketSharp
pi = pi.Slice(1);
}
- format = LeftPart(pi, '/');
+ format = pi.LeftPart('/');
if (format.Length > FormatMaxLength)
{
return null;
}
}
- format = LeftPart(format, '.');
+ format = format.LeftPart('.');
if (format.Contains("json", StringComparison.OrdinalIgnoreCase))
{
return "application/json";
@@ -241,16 +242,5 @@ namespace Emby.Server.Implementations.SocketSharp
return null;
}
-
- public static ReadOnlySpan LeftPart(ReadOnlySpan strVal, char needle)
- {
- if (strVal == null)
- {
- return null;
- }
-
- var pos = strVal.IndexOf(needle);
- return pos == -1 ? strVal : strVal.Slice(0, pos);
- }
}
}
diff --git a/Jellyfin.Api/BaseJellyfinApiController.cs b/Jellyfin.Api/BaseJellyfinApiController.cs
index 1f4508e6c..a34f9eb62 100644
--- a/Jellyfin.Api/BaseJellyfinApiController.cs
+++ b/Jellyfin.Api/BaseJellyfinApiController.cs
@@ -1,3 +1,4 @@
+using System.Net.Mime;
using Microsoft.AspNetCore.Mvc;
namespace Jellyfin.Api
@@ -7,6 +8,7 @@ namespace Jellyfin.Api
///
[ApiController]
[Route("[controller]")]
+ [Produces(MediaTypeNames.Application.Json)]
public class BaseJellyfinApiController : ControllerBase
{
}
diff --git a/Jellyfin.Data/DbContexts/Jellyfin.cs b/Jellyfin.Data/DbContexts/Jellyfin.cs
new file mode 100644
index 000000000..fd488ce7d
--- /dev/null
+++ b/Jellyfin.Data/DbContexts/Jellyfin.cs
@@ -0,0 +1,1140 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated from a template.
+//
+// Manual changes to this file may cause unexpected behavior in your application.
+// Manual changes to this file will be overwritten if the code is regenerated.
+//
+// Produced by Entity Framework Visual Editor
+// https://github.com/msawczyn/EFDesigner
+//
+//------------------------------------------------------------------------------
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.ComponentModel.DataAnnotations.Schema;
+using Microsoft.EntityFrameworkCore;
+
+namespace Jellyfin.Data.DbContexts
+{
+ ///
+ public partial class Jellyfin : DbContext
+ {
+ #region DbSets
+ public virtual Microsoft.EntityFrameworkCore.DbSet Artwork { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Books { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet BookMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Chapters { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Collections { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet CollectionItems { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Companies { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet CompanyMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet CustomItems { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet CustomItemMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Episodes { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet EpisodeMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Genres { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Groups { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Libraries { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet LibraryItems { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet LibraryRoot { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet MediaFiles { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet MediaFileStream { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Metadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviders { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet MetadataProviderIds { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Movies { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet MovieMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbums { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet MusicAlbumMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Permissions { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet People { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet PersonRoles { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Photo { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet PhotoMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Preferences { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet ProviderMappings { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Ratings { get; set; }
+
+ ///
+ /// Repository for global::Jellyfin.Data.Entities.RatingSource - This is the entity to
+ /// store review ratings, not age ratings
+ ///
+ public virtual Microsoft.EntityFrameworkCore.DbSet RatingSources { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Releases { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Seasons { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet SeasonMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Series { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet SeriesMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Tracks { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet TrackMetadata { get; set; }
+ public virtual Microsoft.EntityFrameworkCore.DbSet Users { get; set; }
+ #endregion DbSets
+
+ ///
+ /// Default connection string
+ ///
+ public static string ConnectionString { get; set; } = @"Data Source=jellyfin.db";
+
+ ///
+ public Jellyfin(DbContextOptions options) : base(options)
+ {
+ }
+
+ partial void CustomInit(DbContextOptionsBuilder optionsBuilder);
+
+ ///
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ CustomInit(optionsBuilder);
+ }
+
+ partial void OnModelCreatingImpl(ModelBuilder modelBuilder);
+ partial void OnModelCreatedImpl(ModelBuilder modelBuilder);
+
+ ///
+ protected override void OnModelCreating(ModelBuilder modelBuilder)
+ {
+ base.OnModelCreating(modelBuilder);
+ OnModelCreatingImpl(modelBuilder);
+
+ modelBuilder.HasDefaultSchema("jellyfin");
+
+ modelBuilder.Entity()
+ .ToTable("Artwork")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Path)
+ .HasMaxLength(65535)
+ .IsRequired()
+ .HasField("_Path")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Kind)
+ .IsRequired()
+ .HasField("_Kind")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity().HasIndex(t => t.Kind);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity()
+ .HasMany(x => x.BookMetadata)
+ .WithOne()
+ .HasForeignKey("BookMetadata_BookMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .Property(t => t.ISBN)
+ .HasField("_ISBN")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .HasMany(x => x.Publishers)
+ .WithOne()
+ .HasForeignKey("Company_Publishers_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("Chapter")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Language)
+ .HasMaxLength(3)
+ .IsRequired()
+ .HasField("_Language")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.TimeStart)
+ .IsRequired()
+ .HasField("_TimeStart")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.TimeEnd)
+ .HasField("_TimeEnd")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity()
+ .ToTable("Collection")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasMany(x => x.CollectionItem)
+ .WithOne()
+ .HasForeignKey("CollectionItem_CollectionItem_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("CollectionItem")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasOne(x => x.LibraryItem)
+ .WithOne()
+ .HasForeignKey("LibraryItem_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasOne(x => x.Next)
+ .WithOne()
+ .HasForeignKey("CollectionItem_Next_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasOne(x => x.Previous)
+ .WithOne()
+ .HasForeignKey("CollectionItem_Previous_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("Company")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasMany(x => x.CompanyMetadata)
+ .WithOne()
+ .HasForeignKey("CompanyMetadata_CompanyMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasOne(x => x.Parent)
+ .WithOne()
+ .HasForeignKey("Company_Parent_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .Property(t => t.Description)
+ .HasMaxLength(65535)
+ .HasField("_Description")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Headquarters)
+ .HasMaxLength(255)
+ .HasField("_Headquarters")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Country)
+ .HasMaxLength(2)
+ .HasField("_Country")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Homepage)
+ .HasMaxLength(1024)
+ .HasField("_Homepage")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+
+ modelBuilder.Entity()
+ .HasMany(x => x.CustomItemMetadata)
+ .WithOne()
+ .HasForeignKey("CustomItemMetadata_CustomItemMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+
+
+ modelBuilder.Entity()
+ .Property(t => t.EpisodeNumber)
+ .HasField("_EpisodeNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.EpisodeMetadata)
+ .WithOne()
+ .HasForeignKey("EpisodeMetadata_EpisodeMetadata_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .Property(t => t.Outline)
+ .HasMaxLength(1024)
+ .HasField("_Outline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Plot)
+ .HasMaxLength(65535)
+ .HasField("_Plot")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Tagline)
+ .HasMaxLength(1024)
+ .HasField("_Tagline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+
+ modelBuilder.Entity()
+ .ToTable("Genre")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(255)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity().HasIndex(t => t.Name)
+ .IsUnique();
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity()
+ .ToTable("Groups")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(255)
+ .IsRequired();
+ modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken();
+ modelBuilder.Entity()
+ .HasMany(x => x.GroupPermissions)
+ .WithOne()
+ .HasForeignKey("Permission_GroupPermissions_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.ProviderMappings)
+ .WithOne()
+ .HasForeignKey("ProviderMapping_ProviderMappings_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Preferences)
+ .WithOne()
+ .HasForeignKey("Preference_Preferences_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("Library")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity()
+ .ToTable("LibraryItem")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.UrlId)
+ .IsRequired()
+ .HasField("_UrlId")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity().HasIndex(t => t.UrlId)
+ .IsUnique();
+ modelBuilder.Entity()
+ .Property(t => t.DateAdded)
+ .IsRequired()
+ .HasField("_DateAdded")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasOne(x => x.LibraryRoot)
+ .WithOne()
+ .HasForeignKey("LibraryRoot_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("LibraryRoot")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Path)
+ .HasMaxLength(65535)
+ .IsRequired()
+ .HasField("_Path")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.NetworkPath)
+ .HasMaxLength(65535)
+ .HasField("_NetworkPath")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasOne(x => x.Library)
+ .WithOne()
+ .HasForeignKey("Library_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("MediaFile")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Path)
+ .HasMaxLength(65535)
+ .IsRequired()
+ .HasField("_Path")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Kind)
+ .IsRequired()
+ .HasField("_Kind")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasMany(x => x.MediaFileStreams)
+ .WithOne()
+ .HasForeignKey("MediaFileStream_MediaFileStreams_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("MediaFileStream")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.StreamNumber)
+ .IsRequired()
+ .HasField("_StreamNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity()
+ .ToTable("Metadata")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Title)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Title")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.OriginalTitle)
+ .HasMaxLength(1024)
+ .HasField("_OriginalTitle")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.SortTitle)
+ .HasMaxLength(1024)
+ .HasField("_SortTitle")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Language)
+ .HasMaxLength(3)
+ .IsRequired()
+ .HasField("_Language")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.ReleaseDate)
+ .HasField("_ReleaseDate")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.DateAdded)
+ .IsRequired()
+ .HasField("_DateAdded")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.DateModified)
+ .IsRequired()
+ .HasField("_DateModified")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasMany(x => x.PersonRoles)
+ .WithOne()
+ .HasForeignKey("PersonRole_PersonRoles_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Genres)
+ .WithOne()
+ .HasForeignKey("Genre_Genres_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Artwork)
+ .WithOne()
+ .HasForeignKey("Artwork_Artwork_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Ratings)
+ .WithOne()
+ .HasForeignKey("Rating_Ratings_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Sources)
+ .WithOne()
+ .HasForeignKey("MetadataProviderId_Sources_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("MetadataProvider")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+
+ modelBuilder.Entity()
+ .ToTable("MetadataProviderId")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.ProviderId)
+ .HasMaxLength(255)
+ .IsRequired()
+ .HasField("_ProviderId")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasOne(x => x.MetadataProvider)
+ .WithOne()
+ .HasForeignKey("MetadataProvider_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.MovieMetadata)
+ .WithOne()
+ .HasForeignKey("MovieMetadata_MovieMetadata_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .Property(t => t.Outline)
+ .HasMaxLength(1024)
+ .HasField("_Outline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Plot)
+ .HasMaxLength(65535)
+ .HasField("_Plot")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Tagline)
+ .HasMaxLength(1024)
+ .HasField("_Tagline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Country)
+ .HasMaxLength(2)
+ .HasField("_Country")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .HasMany(x => x.Studios)
+ .WithOne()
+ .HasForeignKey("Company_Studios_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .HasMany(x => x.MusicAlbumMetadata)
+ .WithOne()
+ .HasForeignKey("MusicAlbumMetadata_MusicAlbumMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Tracks)
+ .WithOne()
+ .HasForeignKey("Track_Tracks_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .Property(t => t.Barcode)
+ .HasMaxLength(255)
+ .HasField("_Barcode")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.LabelNumber)
+ .HasMaxLength(255)
+ .HasField("_LabelNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Country)
+ .HasMaxLength(2)
+ .HasField("_Country")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .HasMany(x => x.Labels)
+ .WithOne()
+ .HasForeignKey("Company_Labels_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("Permissions")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Kind)
+ .IsRequired()
+ .HasField("_Kind")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Value)
+ .IsRequired();
+ modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken();
+
+ modelBuilder.Entity()
+ .ToTable("Person")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.UrlId)
+ .IsRequired()
+ .HasField("_UrlId")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.SourceId)
+ .HasMaxLength(255)
+ .HasField("_SourceId")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.DateAdded)
+ .IsRequired()
+ .HasField("_DateAdded")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.DateModified)
+ .IsRequired()
+ .HasField("_DateModified")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasMany(x => x.Sources)
+ .WithOne()
+ .HasForeignKey("MetadataProviderId_Sources_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("PersonRole")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Role)
+ .HasMaxLength(1024)
+ .HasField("_Role")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Type)
+ .IsRequired()
+ .HasField("_Type")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasOne(x => x.Person)
+ .WithOne()
+ .HasForeignKey("Person_Id")
+ .IsRequired()
+ .OnDelete(DeleteBehavior.Cascade);
+ modelBuilder.Entity()
+ .HasOne(x => x.Artwork)
+ .WithOne()
+ .HasForeignKey("Artwork_Artwork_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Sources)
+ .WithOne()
+ .HasForeignKey("MetadataProviderId_Sources_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .HasMany(x => x.PhotoMetadata)
+ .WithOne()
+ .HasForeignKey("PhotoMetadata_PhotoMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Releases)
+ .WithOne()
+ .HasForeignKey("Release_Releases_Id")
+ .IsRequired();
+
+
+ modelBuilder.Entity()
+ .ToTable("Preferences")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Kind)
+ .IsRequired();
+ modelBuilder.Entity()
+ .Property(t => t.Value)
+ .HasMaxLength(65535)
+ .IsRequired();
+ modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken();
+
+ modelBuilder.Entity()
+ .ToTable("ProviderMappings")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.ProviderName)
+ .HasMaxLength(255)
+ .IsRequired();
+ modelBuilder.Entity()
+ .Property(t => t.ProviderSecrets)
+ .HasMaxLength(65535)
+ .IsRequired();
+ modelBuilder.Entity()
+ .Property(t => t.ProviderData)
+ .HasMaxLength(65535)
+ .IsRequired();
+ modelBuilder.Entity().Property("Timestamp").IsConcurrencyToken();
+
+ modelBuilder.Entity()
+ .ToTable("Rating")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Value)
+ .IsRequired()
+ .HasField("_Value")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Votes)
+ .HasField("_Votes")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasOne(x => x.RatingType)
+ .WithOne()
+ .HasForeignKey("RatingSource_RatingType_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("RatingType")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.MaximumValue)
+ .IsRequired()
+ .HasField("_MaximumValue")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.MinimumValue)
+ .IsRequired()
+ .HasField("_MinimumValue")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasOne(x => x.Source)
+ .WithOne()
+ .HasForeignKey("MetadataProviderId_Source_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .ToTable("Release")
+ .HasKey(t => t.Id);
+ modelBuilder.Entity()
+ .Property(t => t.Id)
+ .IsRequired()
+ .HasField("_Id")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .ValueGeneratedOnAdd();
+ modelBuilder.Entity()
+ .Property(t => t.Name)
+ .HasMaxLength(1024)
+ .IsRequired()
+ .HasField("_Name")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .Property(t => t.Timestamp)
+ .IsRequired()
+ .HasField("_Timestamp")
+ .UsePropertyAccessMode(PropertyAccessMode.Property)
+ .IsRowVersion();
+ modelBuilder.Entity()
+ .HasMany(x => x.MediaFiles)
+ .WithOne()
+ .HasForeignKey("MediaFile_MediaFiles_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Chapters)
+ .WithOne()
+ .HasForeignKey("Chapter_Chapters_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .Property(t => t.SeasonNumber)
+ .HasField("_SeasonNumber")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+ modelBuilder.Entity()
+ .HasMany(x => x.SeasonMetadata)
+ .WithOne()
+ .HasForeignKey("SeasonMetadata_SeasonMetadata_Id")
+ .IsRequired();
+ modelBuilder.Entity()
+ .HasMany(x => x.Episodes)
+ .WithOne()
+ .HasForeignKey("Episode_Episodes_Id")
+ .IsRequired();
+
+ modelBuilder.Entity()
+ .Property(t => t.Outline)
+ .HasMaxLength(1024)
+ .HasField("_Outline")
+ .UsePropertyAccessMode(PropertyAccessMode.Property);
+
+ modelBuilder.Entity