Explorar o código

added artist/slbum xml savers

Luke Pulverenti %!s(int64=12) %!d(string=hai) anos
pai
achega
10caa7ff83

+ 13 - 0
MediaBrowser.Controller/Entities/BaseItem.cs

@@ -1142,6 +1142,19 @@ namespace MediaBrowser.Controller.Entities
             }
         }
 
+        public void AddTag(string name)
+        {
+            if (string.IsNullOrWhiteSpace(name))
+            {
+                throw new ArgumentNullException("name");
+            }
+
+            if (!Tags.Contains(name, StringComparer.OrdinalIgnoreCase))
+            {
+                Tags.Add(name);
+            }
+        }
+
         /// <summary>
         /// Adds a tagline to the item
         /// </summary>

+ 35 - 0
MediaBrowser.Controller/Providers/BaseItemXmlParser.cs

@@ -65,6 +65,7 @@ namespace MediaBrowser.Controller.Providers
             item.Studios.Clear();
             item.Genres.Clear();
             item.People.Clear();
+            item.Tags.Clear();
 
             // Use european encoding as it will accept more characters
             using (var streamReader = new StreamReader(metadataFile, Encoding.GetEncoding("ISO-8859-1")))
@@ -397,6 +398,7 @@ namespace MediaBrowser.Controller.Providers
                         break;
                     }
 
+                case "PremiereDate":
                 case "FirstAired":
                     {
                         var firstAired = reader.ReadElementContentAsString();
@@ -453,6 +455,10 @@ namespace MediaBrowser.Controller.Providers
                     FetchFromGenresNode(reader.ReadSubtree(), item);
                     break;
 
+                case "Tags":
+                    FetchFromTagsNode(reader.ReadSubtree(), item);
+                    break;
+
                 case "Persons":
                     FetchDataFromPersonsNode(reader.ReadSubtree(), item);
                     break;
@@ -539,6 +545,35 @@ namespace MediaBrowser.Controller.Providers
             }
         }
 
+        private void FetchFromTagsNode(XmlReader reader, T item)
+        {
+            reader.MoveToContent();
+
+            while (reader.Read())
+            {
+                if (reader.NodeType == XmlNodeType.Element)
+                {
+                    switch (reader.Name)
+                    {
+                        case "Tag":
+                            {
+                                var tag = reader.ReadElementContentAsString();
+
+                                if (!string.IsNullOrWhiteSpace(tag))
+                                {
+                                    item.AddTagline(tag);
+                                }
+                                break;
+                            }
+
+                        default:
+                            reader.Skip();
+                            break;
+                    }
+                }
+            }
+        }
+
         /// <summary>
         /// Fetches the data from persons node.
         /// </summary>

+ 2 - 1
MediaBrowser.Providers/FolderProviderFromXml.cs

@@ -15,7 +15,8 @@ namespace MediaBrowser.Providers
     /// </summary>
     public class FolderProviderFromXml : BaseMetadataProvider
     {
-        public FolderProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager) : base(logManager, configurationManager)
+        public FolderProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+            : base(logManager, configurationManager)
         {
         }
 

+ 4 - 2
MediaBrowser.Providers/MediaBrowser.Providers.csproj

@@ -64,6 +64,7 @@
     <Compile Include="Movies\OpenMovieDatabaseProvider.cs" />
     <Compile Include="Movies\PersonProviderFromXml.cs" />
     <Compile Include="Movies\TmdbPersonProvider.cs" />
+    <Compile Include="Music\ArtistProviderFromXml.cs" />
     <Compile Include="Music\ArtistsPostScanTask.cs" />
     <Compile Include="Music\FanArtAlbumProvider.cs" />
     <Compile Include="Music\FanArtArtistByNameProvider.cs" />
@@ -74,14 +75,15 @@
     <Compile Include="Music\LastfmArtistProvider.cs" />
     <Compile Include="Music\LastfmBaseProvider.cs" />
     <Compile Include="Music\LastfmHelper.cs" />
-    <Compile Include="Music\MusicArtistProviderFromJson.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Savers\AlbumXmlSaver.cs" />
+    <Compile Include="Savers\ArtistXmlSaver.cs" />
     <Compile Include="Savers\EpisodeXmlSaver.cs" />
     <Compile Include="Savers\FolderXmlSaver.cs" />
     <Compile Include="Savers\MovieXmlSaver.cs" />
     <Compile Include="Savers\PersonXmlSaver.cs" />
     <Compile Include="Savers\SeriesXmlSaver.cs" />
-    <Compile Include="Savers\XmlHelpers.cs" />
+    <Compile Include="Savers\XmlSaverHelpers.cs" />
     <Compile Include="TV\EpisodeImageFromMediaLocationProvider.cs" />
     <Compile Include="TV\EpisodeIndexNumberProvider.cs" />
     <Compile Include="TV\EpisodeProviderFromXml.cs" />

+ 1 - 4
MediaBrowser.Providers/Movies/FanArtMovieUpdatesPrescanTask.cs

@@ -1,11 +1,9 @@
 using MediaBrowser.Common.Net;
 using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Providers.Music;
 using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Net;
 using MediaBrowser.Model.Serialization;
+using MediaBrowser.Providers.Music;
 using System;
 using System.Collections.Generic;
 using System.Globalization;
@@ -14,7 +12,6 @@ using System.Linq;
 using System.Text;
 using System.Threading;
 using System.Threading.Tasks;
-using MediaBrowser.Providers.Music;
 
 namespace MediaBrowser.Providers.Movies
 {

+ 107 - 0
MediaBrowser.Providers/Music/ArtistProviderFromXml.cs

@@ -0,0 +1,107 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Logging;
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Providers.Music
+{
+    class ArtistProviderFromXml : BaseMetadataProvider
+    {
+        public ArtistProviderFromXml(ILogManager logManager, IServerConfigurationManager configurationManager)
+            : base(logManager, configurationManager)
+        {
+        }
+
+        /// <summary>
+        /// Supportses the specified item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        public override bool Supports(BaseItem item)
+        {
+            return (item is Artist || item is MusicArtist) && item.LocationType == LocationType.FileSystem;
+        }
+
+        /// <summary>
+        /// Gets the priority.
+        /// </summary>
+        /// <value>The priority.</value>
+        public override MetadataProviderPriority Priority
+        {
+            get { return MetadataProviderPriority.First; }
+        }
+
+        /// <summary>
+        /// Override this to return the date that should be compared to the last refresh date
+        /// to determine if this provider should be re-fetched.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>DateTime.</returns>
+        protected override DateTime CompareDate(BaseItem item)
+        {
+            var entry = item.MetaLocation != null ? item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, "artist.xml")) : null;
+            return entry != null ? entry.LastWriteTimeUtc : DateTime.MinValue;
+        }
+
+        /// <summary>
+        /// Fetches metadata and returns true or false indicating if any work that requires persistence was done
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="force">if set to <c>true</c> [force].</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task{System.Boolean}.</returns>
+        public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
+        {
+            return Fetch(item, cancellationToken);
+        }
+
+        /// <summary>
+        /// Fetches the specified item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        private async Task<bool> Fetch(BaseItem item, CancellationToken cancellationToken)
+        {
+            cancellationToken.ThrowIfCancellationRequested();
+
+            var metadataFile = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, "artist.xml"));
+
+            if (metadataFile != null)
+            {
+                var path = metadataFile.FullName;
+
+                await XmlParsingResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false);
+
+                try
+                {
+                    var artist = item as Artist;
+
+                    if (artist != null)
+                    {
+                        new BaseItemXmlParser<Artist>(Logger).Fetch(artist, path, cancellationToken);
+                    }
+                    else
+                    {
+                        new BaseItemXmlParser<MusicArtist>(Logger).Fetch((MusicArtist)item, path, cancellationToken);
+                    }
+                }
+                finally
+                {
+                    XmlParsingResourcePool.Release();
+                }
+
+                SetLastRefreshed(item, DateTime.UtcNow);
+                return true;
+            }
+
+            return false;
+        }
+    }
+}

+ 12 - 23
MediaBrowser.Providers/Music/LastfmAlbumProvider.cs

@@ -7,12 +7,11 @@ using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
+using MoreLinq;
 using System;
-using System.IO;
 using System.Linq;
 using System.Threading;
 using System.Threading.Tasks;
-using MoreLinq;
 
 namespace MediaBrowser.Providers.Music
 {
@@ -20,18 +19,9 @@ namespace MediaBrowser.Providers.Music
     {
         private static readonly Task<string> BlankId = Task.FromResult("");
 
-        private readonly IProviderManager _providerManager;
-
-        /// <summary>
-        /// The name of the local json meta file for this item type
-        /// </summary>
-        protected string LocalMetaFileName { get; set; }
-
-        public LastfmAlbumProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager)
+        public LastfmAlbumProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager)
             : base(jsonSerializer, httpClient, logManager, configurationManager)
         {
-            _providerManager = providerManager;
-            LocalMetaFileName = LastfmHelper.LocalAlbumMetaFileName;
         }
 
         protected override Task<string> FindId(BaseItem item, CancellationToken cancellationToken)
@@ -40,6 +30,11 @@ namespace MediaBrowser.Providers.Music
             return BlankId;
         }
 
+        private bool HasAltMeta(BaseItem item)
+        {
+            return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName("album.xml");
+        }
+        
         /// <summary>
         /// Needses the refresh internal.
         /// </summary>
@@ -48,6 +43,11 @@ namespace MediaBrowser.Providers.Music
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
         {
+            if (HasAltMeta(item))
+            {
+                return false;
+            }
+
             // If song metadata has changed and we don't have an mbid, refresh
             if (string.IsNullOrEmpty(item.GetProviderId(MetadataProviders.Musicbrainz)) &&
                 GetComparisonData(item as MusicAlbum) != providerInfo.FileStamp)
@@ -65,17 +65,6 @@ namespace MediaBrowser.Providers.Music
             if (result != null && result.album != null)
             {
                 LastfmHelper.ProcessAlbumData(item, result.album);
-                //And save locally if indicated
-                if (ConfigurationManager.Configuration.SaveLocalMeta)
-                {
-                    var ms = new MemoryStream();
-                    JsonSerializer.SerializeToStream(result.album, ms);
-
-                    cancellationToken.ThrowIfCancellationRequested();
-
-                    await _providerManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false);
-                    
-                }
             }
 
             BaseProviderInfo data;

+ 2 - 4
MediaBrowser.Providers/Music/LastfmArtistByNameProvider.cs

@@ -3,7 +3,6 @@ using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.Providers;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Serialization;
@@ -26,10 +25,9 @@ namespace MediaBrowser.Providers.Music
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="providerManager">The provider manager.</param>
         /// <param name="libraryManager">The library manager.</param>
-        public LastfmArtistByNameProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, ILibraryManager libraryManager)
-            : base(jsonSerializer, httpClient, logManager, configurationManager, providerManager, libraryManager)
+        public LastfmArtistByNameProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, ILibraryManager libraryManager)
+            : base(jsonSerializer, httpClient, logManager, configurationManager, libraryManager)
         {
         }
 

+ 17 - 30
MediaBrowser.Providers/Music/LastfmArtistProvider.cs

@@ -4,7 +4,6 @@ using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Library;
 using MediaBrowser.Controller.Providers;
-using MediaBrowser.Controller.Providers.Music;
 using MediaBrowser.Model.Entities;
 using MediaBrowser.Model.Logging;
 using MediaBrowser.Model.Net;
@@ -26,37 +25,40 @@ namespace MediaBrowser.Providers.Music
     /// </summary>
     public class LastfmArtistProvider : LastfmBaseProvider
     {
-        /// <summary>
-        /// The _provider manager
-        /// </summary>
-        private readonly IProviderManager _providerManager;
         /// <summary>
         /// The _library manager
         /// </summary>
         protected readonly ILibraryManager LibraryManager;
 
         /// <summary>
-        /// The name of the local json meta file for this item type
-        /// </summary>
-        protected string LocalMetaFileName { get; set; }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="LastfmArtistProvider"/> class.
+        /// Initializes a new instance of the <see cref="LastfmArtistProvider" /> class.
         /// </summary>
         /// <param name="jsonSerializer">The json serializer.</param>
         /// <param name="httpClient">The HTTP client.</param>
         /// <param name="logManager">The log manager.</param>
         /// <param name="configurationManager">The configuration manager.</param>
-        /// <param name="providerManager">The provider manager.</param>
         /// <param name="libraryManager">The library manager.</param>
-        public LastfmArtistProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, IProviderManager providerManager, ILibraryManager libraryManager)
+        public LastfmArtistProvider(IJsonSerializer jsonSerializer, IHttpClient httpClient, ILogManager logManager, IServerConfigurationManager configurationManager, ILibraryManager libraryManager)
             : base(jsonSerializer, httpClient, logManager, configurationManager)
         {
-            _providerManager = providerManager;
             LibraryManager = libraryManager;
-            LocalMetaFileName = LastfmHelper.LocalArtistMetaFileName;
         }
 
+        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
+        {
+            if (HasAltMeta(item))
+            {
+                return false;
+            }
+
+            return base.NeedsRefreshInternal(item, providerInfo);
+        }
+
+        private bool HasAltMeta(BaseItem item)
+        {
+            return item.LocationType == LocationType.FileSystem && item.ResolveArgs.ContainsMetaFileByName("artist.xml");
+        }
+        
         /// <summary>
         /// Finds the id.
         /// </summary>
@@ -259,21 +261,6 @@ namespace MediaBrowser.Providers.Music
             if (result != null && result.artist != null)
             {
                 LastfmHelper.ProcessArtistData(item, result.artist);
-                //And save locally if indicated
-                if (SaveLocalMeta)
-                {
-                    var ms = new MemoryStream();
-                    JsonSerializer.SerializeToStream(result.artist, ms);
-
-                    if (cancellationToken.IsCancellationRequested)
-                    {
-                        ms.Dispose();
-                        cancellationToken.ThrowIfCancellationRequested();
-                    }
-
-                    await _providerManager.SaveToLibraryFilesystem(item, Path.Combine(item.MetaLocation, LocalMetaFileName), ms, cancellationToken).ConfigureAwait(false);
-
-                }
             }
         }
 

+ 0 - 4
MediaBrowser.Providers/Music/LastfmHelper.cs

@@ -1,6 +1,5 @@
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Controller.Providers.Music;
 using MediaBrowser.Model.Entities;
 using System;
 using System.Linq;
@@ -9,9 +8,6 @@ namespace MediaBrowser.Providers.Music
 {
     public static class LastfmHelper
     {
-        public static string LocalArtistMetaFileName = "lastfmartist.json";
-        public static string LocalAlbumMetaFileName = "lastfmalbum.json";
-
         public static void ProcessArtistData(BaseItem artist, LastfmArtist data)
         {
             var yearFormed = 0;

+ 0 - 101
MediaBrowser.Providers/Music/MusicArtistProviderFromJson.cs

@@ -1,101 +0,0 @@
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Entities.Audio;
-using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.Logging;
-using MediaBrowser.Model.Serialization;
-using System;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Providers.Music;
-
-namespace MediaBrowser.Controller.Providers.Music
-{
-    public class MusicArtistProviderFromJson : BaseMetadataProvider
-    {
-        /// <summary>
-        /// Gets the json serializer.
-        /// </summary>
-        /// <value>The json serializer.</value>
-        protected IJsonSerializer JsonSerializer { get; private set; }
-
-        public MusicArtistProviderFromJson(IJsonSerializer jsonSerializer, ILogManager logManager, IServerConfigurationManager configurationManager) 
-            : base(logManager, configurationManager)
-        {
-            if (jsonSerializer == null)
-            {
-                throw new ArgumentNullException("jsonSerializer");
-            }
-            JsonSerializer = jsonSerializer;
-
-        }
-
-        public override Task<bool> FetchAsync(BaseItem item, bool force, CancellationToken cancellationToken)
-        {
-            cancellationToken.ThrowIfCancellationRequested();
-
-            var entry = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, LastfmHelper.LocalArtistMetaFileName));
-            if (entry != null)
-            {
-                // read in our saved meta and pass to processing function
-                var data = JsonSerializer.DeserializeFromFile<LastfmArtist>(entry.FullName);
-
-                cancellationToken.ThrowIfCancellationRequested();
-
-                LastfmHelper.ProcessArtistData(item, data);
-
-                item.SetProviderId(MetadataProviders.Musicbrainz, data.mbid);
-
-                SetLastRefreshed(item, DateTime.UtcNow);
-                return TrueTaskResult;
-            }
-            return FalseTaskResult;
-        }
-
-        public override MetadataProviderPriority Priority
-        {
-            get
-            {
-                return MetadataProviderPriority.First;
-            }
-        }
-
-        public override bool Supports(BaseItem item)
-        {
-            return false;
-        }
-
-        public override bool RequiresInternet
-        {
-            get
-            {
-                return false;
-            }
-        }
-
-        protected override bool NeedsRefreshInternal(BaseItem item, BaseProviderInfo providerInfo)
-        {
-            if (!item.ResolveArgs.ContainsMetaFileByName(LastfmHelper.LocalArtistMetaFileName))
-            {
-                return false; // nothing to read
-            }
-
-            // Need to re-override to jump over intermediate implementation
-            return CompareDate(item) > providerInfo.LastRefreshed;
-        }
-
-        /// <summary>
-        /// Override this to return the date that should be compared to the last refresh date
-        /// to determine if this provider should be re-fetched.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>DateTime.</returns>
-        protected override DateTime CompareDate(BaseItem item)
-        {
-            var entry = item.ResolveArgs.GetMetaFileByPath(Path.Combine(item.MetaLocation, LastfmHelper.LocalArtistMetaFileName));
-            return entry != null ? entry.LastWriteTimeUtc : DateTime.MinValue;
-        }
-
-    }
-}

+ 77 - 0
MediaBrowser.Providers/Savers/AlbumXmlSaver.cs

@@ -0,0 +1,77 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.Movies;
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+namespace MediaBrowser.Providers.Savers
+{
+    class AlbumXmlSaver : IMetadataSaver
+    {
+        private readonly IServerConfigurationManager _config;
+
+        public AlbumXmlSaver(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
+        /// <summary>
+        /// Supportses the specified item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        public bool Supports(BaseItem item)
+        {
+            if (item.LocationType != LocationType.FileSystem)
+            {
+                return false;
+            }
+
+            if (item is MusicAlbum)
+            {
+                return _config.Configuration.SaveLocalMeta;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Saves the specified item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public void Save(BaseItem item, CancellationToken cancellationToken)
+        {
+            var builder = new StringBuilder();
+
+            builder.Append("<Item>");
+
+            XmlSaverHelpers.AddCommonNodes(item, builder);
+
+            builder.Append("</Item>");
+
+            var xmlFilePath = GetSavePath(item);
+
+            XmlSaverHelpers.Save(builder, xmlFilePath);
+
+            // Set last refreshed so that the provider doesn't trigger after the file save
+            PersonProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);
+        }
+
+        /// <summary>
+        /// Gets the save path.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>System.String.</returns>
+        public string GetSavePath(BaseItem item)
+        {
+            return Path.Combine(item.Path, "album.xml");
+        }
+    }
+}

+ 77 - 0
MediaBrowser.Providers/Savers/ArtistXmlSaver.cs

@@ -0,0 +1,77 @@
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
+using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.Movies;
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+namespace MediaBrowser.Providers.Savers
+{
+    class ArtistXmlSaver : IMetadataSaver
+    {
+        private readonly IServerConfigurationManager _config;
+
+        public ArtistXmlSaver(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
+        /// <summary>
+        /// Supportses the specified item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
+        public bool Supports(BaseItem item)
+        {
+            if (item.LocationType != LocationType.FileSystem)
+            {
+                return false;
+            }
+
+            if (item is MusicArtist)
+            {
+                return _config.Configuration.SaveLocalMeta;
+            }
+
+            return item is Artist;
+        }
+
+        /// <summary>
+        /// Saves the specified item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="cancellationToken">The cancellation token.</param>
+        /// <returns>Task.</returns>
+        public void Save(BaseItem item, CancellationToken cancellationToken)
+        {
+            var builder = new StringBuilder();
+
+            builder.Append("<Item>");
+
+            XmlSaverHelpers.AddCommonNodes(item, builder);
+
+            builder.Append("</Item>");
+
+            var xmlFilePath = GetSavePath(item);
+
+            XmlSaverHelpers.Save(builder, xmlFilePath);
+
+            // Set last refreshed so that the provider doesn't trigger after the file save
+            PersonProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);
+        }
+
+        /// <summary>
+        /// Gets the save path.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>System.String.</returns>
+        public string GetSavePath(BaseItem item)
+        {
+            return Path.Combine(item.Path, "artist.xml");
+        }
+    }
+}

+ 3 - 3
MediaBrowser.Providers/Savers/EpisodeXmlSaver.cs

@@ -65,14 +65,14 @@ namespace MediaBrowser.Providers.Savers
                 builder.Append("<FirstAired>" + SecurityElement.Escape(episode.PremiereDate.Value.ToString("yyyy-MM-dd")) + "</FirstAired>");
             }
 
-            XmlHelpers.AddCommonNodes(item, builder);
-            XmlHelpers.AppendMediaInfo(episode, builder);
+            XmlSaverHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AppendMediaInfo(episode, builder);
 
             builder.Append("</Item>");
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlHelpers.Save(builder, xmlFilePath);
+            XmlSaverHelpers.Save(builder, xmlFilePath);
 
             // Set last refreshed so that the provider doesn't trigger after the file save
             EpisodeProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);

+ 14 - 5
MediaBrowser.Providers/Savers/FolderXmlSaver.cs

@@ -1,4 +1,6 @@
-using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Configuration;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
@@ -11,6 +13,13 @@ namespace MediaBrowser.Providers.Savers
 {
     public class FolderXmlSaver : IMetadataSaver
     {
+        private readonly IServerConfigurationManager _config;
+
+        public FolderXmlSaver(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
         /// <summary>
         /// Supportses the specified item.
         /// </summary>
@@ -18,12 +27,12 @@ namespace MediaBrowser.Providers.Savers
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         public bool Supports(BaseItem item)
         {
-            if (item.LocationType != LocationType.FileSystem)
+            if (!_config.Configuration.SaveLocalMeta || item.LocationType != LocationType.FileSystem)
             {
                 return false;
             }
 
-            return item is Folder && !(item is Series) && !(item is BoxSet);
+            return item is Folder && !(item is Series) && !(item is BoxSet) && !(item is MusicArtist) && !(item is MusicAlbum);
         }
 
         /// <summary>
@@ -38,13 +47,13 @@ namespace MediaBrowser.Providers.Savers
 
             builder.Append("<Item>");
 
-            XmlHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes(item, builder);
 
             builder.Append("</Item>");
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlHelpers.Save(builder, xmlFilePath);
+            XmlSaverHelpers.Save(builder, xmlFilePath);
         }
 
         /// <summary>

+ 12 - 4
MediaBrowser.Providers/Savers/MovieXmlSaver.cs

@@ -1,4 +1,4 @@
-using System.Text;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.Movies;
 using MediaBrowser.Controller.Library;
@@ -6,6 +6,7 @@ using MediaBrowser.Model.Entities;
 using MediaBrowser.Providers.Movies;
 using System;
 using System.IO;
+using System.Text;
 using System.Threading;
 
 namespace MediaBrowser.Providers.Savers
@@ -15,6 +16,13 @@ namespace MediaBrowser.Providers.Savers
     /// </summary>
     public class MovieXmlSaver : IMetadataSaver
     {
+        private readonly IServerConfigurationManager _config;
+
+        public MovieXmlSaver(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
         /// <summary>
         /// Supportses the specified item.
         /// </summary>
@@ -22,7 +30,7 @@ namespace MediaBrowser.Providers.Savers
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         public bool Supports(BaseItem item)
         {
-            if (item.LocationType != LocationType.FileSystem)
+            if (!_config.Configuration.SaveLocalMeta || item.LocationType != LocationType.FileSystem)
             {
                 return false;
             }
@@ -50,13 +58,13 @@ namespace MediaBrowser.Providers.Savers
 
             builder.Append("<Title>");
 
-            XmlHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes(item, builder);
 
             builder.Append("</Title>");
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlHelpers.Save(builder, xmlFilePath);
+            XmlSaverHelpers.Save(builder, xmlFilePath);
 
             // Set last refreshed so that the provider doesn't trigger after the file save
             MovieProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);

+ 2 - 2
MediaBrowser.Providers/Savers/PersonXmlSaver.cs

@@ -41,13 +41,13 @@ namespace MediaBrowser.Providers.Savers
 
             builder.Append("<Item>");
 
-            XmlHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes(item, builder);
 
             builder.Append("</Item>");
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlHelpers.Save(builder, xmlFilePath);
+            XmlSaverHelpers.Save(builder, xmlFilePath);
 
             // Set last refreshed so that the provider doesn't trigger after the file save
             PersonProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);

+ 11 - 3
MediaBrowser.Providers/Savers/SeriesXmlSaver.cs

@@ -1,4 +1,5 @@
 using System;
+using MediaBrowser.Controller.Configuration;
 using MediaBrowser.Controller.Entities;
 using MediaBrowser.Controller.Entities.TV;
 using MediaBrowser.Controller.Library;
@@ -13,6 +14,13 @@ namespace MediaBrowser.Providers.Savers
 {
     public class SeriesXmlSaver : IMetadataSaver
     {
+        private readonly IServerConfigurationManager _config;
+
+        public SeriesXmlSaver(IServerConfigurationManager config)
+        {
+            _config = config;
+        }
+
         /// <summary>
         /// Supportses the specified item.
         /// </summary>
@@ -20,7 +28,7 @@ namespace MediaBrowser.Providers.Savers
         /// <returns><c>true</c> if XXXX, <c>false</c> otherwise</returns>
         public bool Supports(BaseItem item)
         {
-            if (item.LocationType != LocationType.FileSystem)
+            if (!_config.Configuration.SaveLocalMeta || item.LocationType != LocationType.FileSystem)
             {
                 return false;
             }
@@ -78,13 +86,13 @@ namespace MediaBrowser.Providers.Savers
                 builder.Append("<Airs_DayOfWeek>" + SecurityElement.Escape(series.AirDays[0].ToString()) + "</Airs_DayOfWeek>");
             }
 
-            XmlHelpers.AddCommonNodes(item, builder);
+            XmlSaverHelpers.AddCommonNodes(item, builder);
 
             builder.Append("</Series>");
 
             var xmlFilePath = GetSavePath(item);
 
-            XmlHelpers.Save(builder, xmlFilePath);
+            XmlSaverHelpers.Save(builder, xmlFilePath);
 
             // Set last refreshed so that the provider doesn't trigger after the file save
             SeriesProviderFromXml.Current.SetLastRefreshed(item, DateTime.UtcNow);

+ 32 - 2
MediaBrowser.Providers/Savers/XmlHelpers.cs → MediaBrowser.Providers/Savers/XmlSaverHelpers.cs

@@ -4,7 +4,6 @@ using MediaBrowser.Model.Entities;
 using System;
 using System.Globalization;
 using System.IO;
-using System.Linq;
 using System.Security;
 using System.Text;
 using System.Xml;
@@ -14,7 +13,7 @@ namespace MediaBrowser.Providers.Savers
     /// <summary>
     /// Class XmlHelpers
     /// </summary>
-    public static class XmlHelpers
+    public static class XmlSaverHelpers
     {
         /// <summary>
         /// The us culture
@@ -34,6 +33,13 @@ namespace MediaBrowser.Providers.Savers
             //Add the new node to the document.
             xmlDocument.InsertBefore(xmlDocument.CreateXmlDeclaration("1.0", "UTF-8", "yes"), xmlDocument.DocumentElement);
 
+            var parentPath = Path.GetDirectoryName(path);
+
+            if (!Directory.Exists(parentPath))
+            {
+                Directory.CreateDirectory(parentPath);
+            }
+
             using (var streamWriter = new StreamWriter(path, false, Encoding.UTF8))
             {
                 xmlDocument.Save(streamWriter);
@@ -94,6 +100,11 @@ namespace MediaBrowser.Providers.Savers
             {
                 builder.Append("<SortTitle>" + SecurityElement.Escape(item.ForcedSortName) + "</SortTitle>");
             }
+
+            if (item.PremiereDate.HasValue)
+            {
+                builder.Append("<PremiereDate>" + SecurityElement.Escape(item.PremiereDate.Value.ToString("yyyy-MM-dd")) + "</PremiereDate>");
+            }
             
             if (item.Budget.HasValue)
             {
@@ -182,6 +193,13 @@ namespace MediaBrowser.Providers.Savers
                 builder.Append("<RottenTomatoesId>" + SecurityElement.Escape(rt) + "</RottenTomatoesId>");
             }
 
+            var mbz = item.GetProviderId(MetadataProviders.Musicbrainz);
+
+            if (!string.IsNullOrEmpty(mbz))
+            {
+                builder.Append("<MusicbrainzId>" + SecurityElement.Escape(mbz) + "</MusicbrainzId>");
+            }
+            
             var tmdbCollection = item.GetProviderId(MetadataProviders.TmdbCollection);
 
             if (!string.IsNullOrEmpty(tmdbCollection))
@@ -213,6 +231,18 @@ namespace MediaBrowser.Providers.Savers
                 builder.Append("</Studios>");
             }
 
+            if (item.Tags.Count > 0)
+            {
+                builder.Append("<Tags>");
+
+                foreach (var tag in item.Tags)
+                {
+                    builder.Append("<Tag>" + SecurityElement.Escape(tag) + "</Tag>");
+                }
+
+                builder.Append("</Tags>");
+            }
+
             builder.Append("<Added>" + SecurityElement.Escape(item.DateCreated.ToString(UsCulture)) + "</Added>");
         }
 

+ 20 - 22
MediaBrowser.Server.Implementations/Providers/ProviderManager.cs

@@ -83,40 +83,38 @@ namespace MediaBrowser.Server.Implementations.Providers
             libraryManager.ItemUpdated += libraryManager_ItemUpdated;
         }
 
+        private readonly ConcurrentDictionary<string, SemaphoreSlim> _fileLocks = new ConcurrentDictionary<string, SemaphoreSlim>();
+        
         /// <summary>
         /// Handles the ItemUpdated event of the libraryManager control.
         /// </summary>
         /// <param name="sender">The source of the event.</param>
         /// <param name="e">The <see cref="ItemChangeEventArgs"/> instance containing the event data.</param>
-        void libraryManager_ItemUpdated(object sender, ItemChangeEventArgs e)
+        async void libraryManager_ItemUpdated(object sender, ItemChangeEventArgs e)
         {
             var item = e.Item;
 
-            if (ConfigurationManager.Configuration.SaveLocalMeta)
+            foreach (var saver in _savers.Where(i => i.Supports(item)))
             {
-                if (item.LocationType != LocationType.FileSystem)
-                {
-                    return;
-                }
+                var path = saver.GetSavePath(item);
 
-                foreach (var saver in _savers.Where(i => i.Supports(item)))
-                {
-                    var path = saver.GetSavePath(item);
+                var semaphore = _fileLocks.GetOrAdd(path, key => new SemaphoreSlim(1, 1));
 
-                    _directoryWatchers.TemporarilyIgnore(path);
+                await semaphore.WaitAsync().ConfigureAwait(false);
 
-                    try
-                    {
-                        saver.Save(item, CancellationToken.None);
-                    }
-                    catch (Exception ex)
-                    {
-                        _logger.ErrorException("Error in metadata saver", ex);
-                    }
-                    finally
-                    {
-                        _directoryWatchers.RemoveTempIgnore(path);
-                    }
+                try
+                {
+                    _directoryWatchers.TemporarilyIgnore(path);
+                    saver.Save(item, CancellationToken.None);
+                }
+                catch (Exception ex)
+                {
+                    _logger.ErrorException("Error in metadata saver", ex);
+                }
+                finally
+                {
+                    _directoryWatchers.RemoveTempIgnore(path);
+                    semaphore.Release();
                 }
             }
         }