mirror of
https://github.com/jellyfin/jellyfin.git
synced 2025-07-09 03:04:24 -04:00
commit
a2b10ce5c2
@ -80,7 +80,7 @@ namespace MediaBrowser.Api
|
|||||||
.OrderBy(i => i)
|
.OrderBy(i => i)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
result.Tags = items.OfType<IHasTags>()
|
result.Tags = items
|
||||||
.SelectMany(i => i.Tags)
|
.SelectMany(i => i.Tags)
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.OrderBy(i => i)
|
.OrderBy(i => i)
|
||||||
|
@ -280,11 +280,7 @@ namespace MediaBrowser.Api
|
|||||||
episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber;
|
episode.AbsoluteEpisodeNumber = request.AbsoluteEpisodeNumber;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasTags = item as IHasTags;
|
item.Tags = request.Tags;
|
||||||
if (hasTags != null)
|
|
||||||
{
|
|
||||||
hasTags.Tags = request.Tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
var hasTaglines = item as IHasTaglines;
|
var hasTaglines = item as IHasTaglines;
|
||||||
if (hasTaglines != null)
|
if (hasTaglines != null)
|
||||||
|
@ -116,13 +116,7 @@ namespace MediaBrowser.Api
|
|||||||
|
|
||||||
private static IEnumerable<string> GetTags(BaseItem item)
|
private static IEnumerable<string> GetTags(BaseItem item)
|
||||||
{
|
{
|
||||||
var hasTags = item as IHasTags;
|
return item.Tags;
|
||||||
if (hasTags != null)
|
|
||||||
{
|
|
||||||
return hasTags.Tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new List<string>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> GetKeywords(BaseItem item)
|
private static IEnumerable<string> GetKeywords(BaseItem item)
|
||||||
|
@ -113,7 +113,7 @@ namespace MediaBrowser.Api
|
|||||||
config.EnableCustomPathSubFolders = true;
|
config.EnableCustomPathSubFolders = true;
|
||||||
config.EnableStandaloneMusicKeys = true;
|
config.EnableStandaloneMusicKeys = true;
|
||||||
config.EnableCaseSensitiveItemIds = true;
|
config.EnableCaseSensitiveItemIds = true;
|
||||||
config.SchemaVersion = 79;
|
config.SchemaVersion = 87;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Post(UpdateStartupConfiguration request)
|
public void Post(UpdateStartupConfiguration request)
|
||||||
|
@ -333,12 +333,7 @@ namespace MediaBrowser.Api.UserLibrary
|
|||||||
var tags = request.GetTags();
|
var tags = request.GetTags();
|
||||||
if (tags.Length > 0)
|
if (tags.Length > 0)
|
||||||
{
|
{
|
||||||
var hasTags = i as IHasTags;
|
if (!tags.Any(v => i.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
||||||
if (hasTags == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ namespace MediaBrowser.Controller.Entities.Audio
|
|||||||
IHasArtist,
|
IHasArtist,
|
||||||
IHasMusicGenres,
|
IHasMusicGenres,
|
||||||
IHasLookupInfo<SongInfo>,
|
IHasLookupInfo<SongInfo>,
|
||||||
IHasTags,
|
|
||||||
IHasMediaSources,
|
IHasMediaSources,
|
||||||
IThemeMedia,
|
IThemeMedia,
|
||||||
IArchivable
|
IArchivable
|
||||||
|
@ -1397,15 +1397,10 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
|
|
||||||
private bool IsVisibleViaTags(User user)
|
private bool IsVisibleViaTags(User user)
|
||||||
{
|
{
|
||||||
var hasTags = this as IHasTags;
|
var policy = user.Policy;
|
||||||
|
if (policy.BlockedTags.Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
|
||||||
if (hasTags != null)
|
|
||||||
{
|
{
|
||||||
var policy = user.Policy;
|
return false;
|
||||||
if (policy.BlockedTags.Any(i => hasTags.Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -6,7 +6,7 @@ using MediaBrowser.Model.Entities;
|
|||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
public class Book : BaseItem, IHasTags, IHasLookupInfo<BookInfo>, IHasSeries
|
public class Book : BaseItem, IHasLookupInfo<BookInfo>, IHasSeries
|
||||||
{
|
{
|
||||||
[IgnoreDataMember]
|
[IgnoreDataMember]
|
||||||
public override string MediaType
|
public override string MediaType
|
||||||
|
@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class Folder
|
/// Class Folder
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Folder : BaseItem, IHasThemeMedia, IHasTags
|
public class Folder : BaseItem, IHasThemeMedia
|
||||||
{
|
{
|
||||||
public static IUserManager UserManager { get; set; }
|
public static IUserManager UserManager { get; set; }
|
||||||
public static IUserViewManager UserViewManager { get; set; }
|
public static IUserViewManager UserViewManager { get; set; }
|
||||||
@ -889,12 +889,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (query.ImageTypes.Length > 0)
|
|
||||||
{
|
|
||||||
Logger.Debug("Query requires post-filtering due to ImageTypes");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply studio filter
|
// Apply studio filter
|
||||||
if (query.StudioIds.Length > 0)
|
if (query.StudioIds.Length > 0)
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasTags, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
|
public class Game : BaseItem, IHasTrailers, IHasThemeMedia, IHasScreenshots, ISupportsPlaceHolders, IHasLookupInfo<GameInfo>
|
||||||
{
|
{
|
||||||
public List<Guid> ThemeSongIds { get; set; }
|
public List<Guid> ThemeSongIds { get; set; }
|
||||||
public List<Guid> ThemeVideoIds { get; set; }
|
public List<Guid> ThemeVideoIds { get; set; }
|
||||||
|
@ -5,7 +5,7 @@ using System.Runtime.Serialization;
|
|||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
public class Photo : BaseItem, IHasTags, IHasTaglines
|
public class Photo : BaseItem, IHasTaglines
|
||||||
{
|
{
|
||||||
public List<string> Taglines { get; set; }
|
public List<string> Taglines { get; set; }
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Class Studio
|
/// Class Studio
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Studio : BaseItem, IItemByName, IHasTags
|
public class Studio : BaseItem, IItemByName
|
||||||
{
|
{
|
||||||
public override List<string> GetUserDataKeys()
|
public override List<string> GetUserDataKeys()
|
||||||
{
|
{
|
||||||
|
@ -1,24 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace MediaBrowser.Controller.Entities
|
namespace MediaBrowser.Controller.Entities
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Interface IHasTags
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasTags
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the tags.
|
|
||||||
/// </summary>
|
|
||||||
/// <value>The tags.</value>
|
|
||||||
List<string> Tags { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TagExtensions
|
public static class TagExtensions
|
||||||
{
|
{
|
||||||
public static void AddTag(this IHasTags item, string name)
|
public static void AddTag(this BaseItem item, string name)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
if (string.IsNullOrWhiteSpace(name))
|
||||||
{
|
{
|
@ -1646,12 +1646,7 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
var tags = query.Tags;
|
var tags = query.Tags;
|
||||||
if (tags.Length > 0)
|
if (tags.Length > 0)
|
||||||
{
|
{
|
||||||
var hasTags = item as IHasTags;
|
if (!tags.Any(v => item.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
||||||
if (hasTags == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (!tags.Any(v => hasTags.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ namespace MediaBrowser.Controller.Entities
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class Video : BaseItem,
|
public class Video : BaseItem,
|
||||||
IHasAspectRatio,
|
IHasAspectRatio,
|
||||||
IHasTags,
|
|
||||||
ISupportsPlaceHolders,
|
ISupportsPlaceHolders,
|
||||||
IHasMediaSources,
|
IHasMediaSources,
|
||||||
IHasShortOverview,
|
IHasShortOverview,
|
||||||
|
@ -154,7 +154,6 @@
|
|||||||
<Compile Include="Entities\IHasSpecialFeatures.cs" />
|
<Compile Include="Entities\IHasSpecialFeatures.cs" />
|
||||||
<Compile Include="Entities\IHasStartDate.cs" />
|
<Compile Include="Entities\IHasStartDate.cs" />
|
||||||
<Compile Include="Entities\IHasTaglines.cs" />
|
<Compile Include="Entities\IHasTaglines.cs" />
|
||||||
<Compile Include="Entities\IHasTags.cs" />
|
|
||||||
<Compile Include="Entities\IHasThemeMedia.cs" />
|
<Compile Include="Entities\IHasThemeMedia.cs" />
|
||||||
<Compile Include="Entities\IHasTrailers.cs" />
|
<Compile Include="Entities\IHasTrailers.cs" />
|
||||||
<Compile Include="Entities\IHasUserData.cs" />
|
<Compile Include="Entities\IHasUserData.cs" />
|
||||||
@ -177,6 +176,7 @@
|
|||||||
<Compile Include="Entities\PhotoAlbum.cs" />
|
<Compile Include="Entities\PhotoAlbum.cs" />
|
||||||
<Compile Include="Entities\Share.cs" />
|
<Compile Include="Entities\Share.cs" />
|
||||||
<Compile Include="Entities\SourceType.cs" />
|
<Compile Include="Entities\SourceType.cs" />
|
||||||
|
<Compile Include="Entities\TagExtensions.cs" />
|
||||||
<Compile Include="Entities\UserView.cs" />
|
<Compile Include="Entities\UserView.cs" />
|
||||||
<Compile Include="Entities\UserViewBuilder.cs" />
|
<Compile Include="Entities\UserViewBuilder.cs" />
|
||||||
<Compile Include="FileOrganization\IFileOrganizationService.cs" />
|
<Compile Include="FileOrganization\IFileOrganizationService.cs" />
|
||||||
|
@ -803,11 +803,7 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
{
|
{
|
||||||
using (var subtree = reader.ReadSubtree())
|
using (var subtree = reader.ReadSubtree())
|
||||||
{
|
{
|
||||||
var hasTags = item as IHasTags;
|
FetchFromTagsNode(subtree, item);
|
||||||
if (hasTags != null)
|
|
||||||
{
|
|
||||||
FetchFromTagsNode(subtree, hasTags);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -1066,7 +1062,7 @@ namespace MediaBrowser.Controller.Providers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FetchFromTagsNode(XmlReader reader, IHasTags item)
|
private void FetchFromTagsNode(XmlReader reader, BaseItem item)
|
||||||
{
|
{
|
||||||
reader.MoveToContent();
|
reader.MoveToContent();
|
||||||
|
|
||||||
|
@ -593,20 +593,16 @@ namespace MediaBrowser.LocalMetadata.Savers
|
|||||||
builder.Append("</Studios>");
|
builder.Append("</Studios>");
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasTags = item as IHasTags;
|
if (item.Tags.Count > 0)
|
||||||
if (hasTags != null)
|
|
||||||
{
|
{
|
||||||
if (hasTags.Tags.Count > 0)
|
builder.Append("<Tags>");
|
||||||
|
|
||||||
|
foreach (var tag in item.Tags)
|
||||||
{
|
{
|
||||||
builder.Append("<Tags>");
|
builder.Append("<Tag>" + SecurityElement.Escape(tag) + "</Tag>");
|
||||||
|
|
||||||
foreach (var tag in hasTags.Tags)
|
|
||||||
{
|
|
||||||
builder.Append("<Tag>" + SecurityElement.Escape(tag) + "</Tag>");
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.Append("</Tags>");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder.Append("</Tags>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.Keywords.Count > 0)
|
if (item.Keywords.Count > 0)
|
||||||
|
@ -48,7 +48,8 @@ namespace MediaBrowser.Model.Configuration
|
|||||||
public bool RememberAudioSelections { get; set; }
|
public bool RememberAudioSelections { get; set; }
|
||||||
public bool RememberSubtitleSelections { get; set; }
|
public bool RememberSubtitleSelections { get; set; }
|
||||||
public bool EnableNextEpisodeAutoPlay { get; set; }
|
public bool EnableNextEpisodeAutoPlay { get; set; }
|
||||||
|
public bool DisplayFoldersView { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
|
/// Initializes a new instance of the <see cref="UserConfiguration" /> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -9,50 +9,50 @@ namespace MediaBrowser.Model.Entities
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The primary
|
/// The primary
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Primary,
|
Primary = 0,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The art
|
/// The art
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Art,
|
Art = 1,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The backdrop
|
/// The backdrop
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Backdrop,
|
Backdrop = 2,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The banner
|
/// The banner
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Banner,
|
Banner = 3,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The logo
|
/// The logo
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Logo,
|
Logo = 4,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The thumb
|
/// The thumb
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Thumb,
|
Thumb = 5,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The disc
|
/// The disc
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Disc,
|
Disc = 6,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The box
|
/// The box
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Box,
|
Box = 7,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The screenshot
|
/// The screenshot
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Screenshot,
|
Screenshot = 8,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The menu
|
/// The menu
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Menu,
|
Menu = 9,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The chapter image
|
/// The chapter image
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Chapter,
|
Chapter = 10,
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The box rear
|
/// The box rear
|
||||||
/// </summary>
|
/// </summary>
|
||||||
BoxRear
|
BoxRear = 11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -151,15 +151,9 @@ namespace MediaBrowser.Providers.Manager
|
|||||||
|
|
||||||
if (!lockedFields.Contains(MetadataFields.Tags))
|
if (!lockedFields.Contains(MetadataFields.Tags))
|
||||||
{
|
{
|
||||||
var sourceHasTags = source as IHasTags;
|
if (replaceData || target.Tags.Count == 0)
|
||||||
var targetHasTags = target as IHasTags;
|
|
||||||
|
|
||||||
if (sourceHasTags != null && targetHasTags != null)
|
|
||||||
{
|
{
|
||||||
if (replaceData || targetHasTags.Tags.Count == 0)
|
target.Tags = source.Tags;
|
||||||
{
|
|
||||||
targetHasTags.Tags = sourceHasTags.Tags;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -969,16 +969,7 @@ namespace MediaBrowser.Server.Implementations.Dto
|
|||||||
|
|
||||||
if (fields.Contains(ItemFields.Tags))
|
if (fields.Contains(ItemFields.Tags))
|
||||||
{
|
{
|
||||||
var hasTags = item as IHasTags;
|
dto.Tags = item.Tags;
|
||||||
if (hasTags != null)
|
|
||||||
{
|
|
||||||
dto.Tags = hasTags.Tags;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dto.Tags == null)
|
|
||||||
{
|
|
||||||
dto.Tags = new List<string>();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields.Contains(ItemFields.Keywords))
|
if (fields.Contains(ItemFields.Keywords))
|
||||||
|
@ -562,9 +562,10 @@ namespace MediaBrowser.Server.Implementations.FileOrganization
|
|||||||
series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
|
series = _libraryManager.GetItemList(new Controller.Entities.InternalItemsQuery
|
||||||
{
|
{
|
||||||
IncludeItemTypes = new[] { typeof(Series).Name },
|
IncludeItemTypes = new[] { typeof(Series).Name },
|
||||||
Recursive = true
|
Recursive = true,
|
||||||
}).Cast<Series>()
|
Name = info.ItemName
|
||||||
.FirstOrDefault(i => string.Equals(i.Name, info.ItemName, StringComparison.OrdinalIgnoreCase));
|
|
||||||
|
}).Cast<Series>().FirstOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,8 @@ using System.Data.SQLite;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using MediaBrowser.Controller.Entities;
|
||||||
|
using MediaBrowser.Model.Entities;
|
||||||
using MediaBrowser.Model.Logging;
|
using MediaBrowser.Model.Logging;
|
||||||
|
|
||||||
namespace MediaBrowser.Server.Implementations.Persistence
|
namespace MediaBrowser.Server.Implementations.Persistence
|
||||||
@ -48,7 +50,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
|
|
||||||
public static void BindGetSimilarityScore(IDbConnection connection, ILogger logger)
|
public static void BindGetSimilarityScore(IDbConnection connection, ILogger logger)
|
||||||
{
|
{
|
||||||
var sqlConnection = (SQLiteConnection) connection;
|
var sqlConnection = (SQLiteConnection)connection;
|
||||||
SimiliarToFunction.Logger = logger;
|
SimiliarToFunction.Logger = logger;
|
||||||
sqlConnection.BindFunction(new SimiliarToFunction());
|
sqlConnection.BindFunction(new SimiliarToFunction());
|
||||||
}
|
}
|
||||||
@ -64,28 +66,38 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SQLiteFunction(Name = "GetSimilarityScore", Arguments = 12, FuncType = FunctionType.Scalar)]
|
[SQLiteFunction(Name = "GetSimilarityScore", Arguments = 6, FuncType = FunctionType.Scalar)]
|
||||||
public class SimiliarToFunction : SQLiteFunction
|
public class SimiliarToFunction : SQLiteFunction
|
||||||
{
|
{
|
||||||
internal static ILogger Logger;
|
internal static ILogger Logger;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, int> _personTypeScores = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
{
|
||||||
|
{ PersonType.Actor, 3},
|
||||||
|
{ PersonType.Director, 5},
|
||||||
|
{ PersonType.Composer, 2},
|
||||||
|
{ PersonType.GuestStar, 3},
|
||||||
|
{ PersonType.Writer, 2},
|
||||||
|
{ PersonType.Conductor, 2},
|
||||||
|
{ PersonType.Producer, 2},
|
||||||
|
{ PersonType.Lyricist, 2}
|
||||||
|
};
|
||||||
|
|
||||||
public override object Invoke(object[] args)
|
public override object Invoke(object[] args)
|
||||||
{
|
{
|
||||||
var score = 0;
|
var score = 0;
|
||||||
|
|
||||||
var inputOfficialRating = args[0] as string;
|
// Official rating equals
|
||||||
var rowOfficialRating = args[1] as string;
|
if ((long)args[0] == 1)
|
||||||
if (!string.IsNullOrWhiteSpace(inputOfficialRating) && string.Equals(inputOfficialRating, rowOfficialRating))
|
|
||||||
{
|
{
|
||||||
score += 10;
|
score += 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
long? inputYear = args[2] == null ? (long?)null : (long)args[2];
|
// Year difference
|
||||||
long? rowYear = args[3] == null ? (long?)null : (long)args[3];
|
long? yearDifference = args[1] == null ? (long?)null : (long)args[1];
|
||||||
|
if (yearDifference.HasValue)
|
||||||
if (inputYear.HasValue && rowYear.HasValue)
|
|
||||||
{
|
{
|
||||||
var diff = Math.Abs(inputYear.Value - rowYear.Value);
|
var diff = Math.Abs(yearDifference.Value);
|
||||||
|
|
||||||
// Add if they came out within the same decade
|
// Add if they came out within the same decade
|
||||||
if (diff < 10)
|
if (diff < 10)
|
||||||
@ -101,52 +113,28 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
}
|
}
|
||||||
|
|
||||||
// genres
|
// genres
|
||||||
score += GetListScore(args, 4, 5);
|
score += Convert.ToInt32((long)args[2]) * 10;
|
||||||
|
|
||||||
// tags
|
// tags
|
||||||
score += GetListScore(args, 6, 7);
|
score += Convert.ToInt32((long)args[3]) * 10;
|
||||||
|
|
||||||
// keywords
|
// # of common keywords
|
||||||
score += GetListScore(args, 8, 9);
|
score += Convert.ToInt32((long)args[4]) *10;
|
||||||
|
|
||||||
|
// # of common studios
|
||||||
|
score += Convert.ToInt32((long)args[5]) * 3;
|
||||||
|
|
||||||
// studios
|
// studios
|
||||||
score += GetListScore(args, 10, 11, 3);
|
//score += GetListScore(args, 7, 8, 3);
|
||||||
|
|
||||||
|
//var rowPeopleNamesText = (args[12] as string) ?? string.Empty;
|
||||||
|
//var rowPeopleNames = rowPeopleNamesText.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
|
||||||
// TODO: People
|
//foreach (var name in rowPeopleNames)
|
||||||
// var item2PeopleNames = allPeople.Where(i => i.ItemId == item2.Id)
|
//{
|
||||||
//.Select(i => i.Name)
|
// // TODO: Send along person types
|
||||||
//.Where(i => !string.IsNullOrWhiteSpace(i))
|
// score += 3;
|
||||||
//.DistinctNames()
|
//}
|
||||||
//.ToDictionary(i => i, StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// points += item1People.Where(i => item2PeopleNames.ContainsKey(i.Name)).Sum(i =>
|
|
||||||
// {
|
|
||||||
// if (string.Equals(i.Type, PersonType.Director, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
|
|
||||||
// {
|
|
||||||
// return 5;
|
|
||||||
// }
|
|
||||||
// if (string.Equals(i.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
|
|
||||||
// {
|
|
||||||
// return 3;
|
|
||||||
// }
|
|
||||||
// if (string.Equals(i.Type, PersonType.Composer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Composer, StringComparison.OrdinalIgnoreCase))
|
|
||||||
// {
|
|
||||||
// return 3;
|
|
||||||
// }
|
|
||||||
// if (string.Equals(i.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
|
|
||||||
// {
|
|
||||||
// return 3;
|
|
||||||
// }
|
|
||||||
// if (string.Equals(i.Type, PersonType.Writer, StringComparison.OrdinalIgnoreCase) || string.Equals(i.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
|
|
||||||
// {
|
|
||||||
// return 2;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return 1;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return points;
|
|
||||||
|
|
||||||
//Logger.Debug("Returning score {0}", score);
|
//Logger.Debug("Returning score {0}", score);
|
||||||
return score;
|
return score;
|
||||||
|
@ -88,10 +88,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
private IDbCommand _deleteProviderIdsCommand;
|
private IDbCommand _deleteProviderIdsCommand;
|
||||||
private IDbCommand _saveProviderIdsCommand;
|
private IDbCommand _saveProviderIdsCommand;
|
||||||
|
|
||||||
|
private IDbCommand _deleteImagesCommand;
|
||||||
|
private IDbCommand _saveImagesCommand;
|
||||||
|
|
||||||
private IDbCommand _updateInheritedRatingCommand;
|
private IDbCommand _updateInheritedRatingCommand;
|
||||||
private IDbCommand _updateInheritedTagsCommand;
|
private IDbCommand _updateInheritedTagsCommand;
|
||||||
|
|
||||||
public const int LatestSchemaVersion = 84;
|
public const int LatestSchemaVersion = 87;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
|
/// Initializes a new instance of the <see cref="SqliteItemRepository"/> class.
|
||||||
@ -144,10 +147,14 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
|
|
||||||
"create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT)",
|
"create table if not exists ItemValues (ItemId GUID, Type INT, Value TEXT)",
|
||||||
"create index if not exists idx_ItemValues on ItemValues(ItemId)",
|
"create index if not exists idx_ItemValues on ItemValues(ItemId)",
|
||||||
|
"create index if not exists idx_ItemValues2 on ItemValues(ItemId,Type)",
|
||||||
|
|
||||||
"create table if not exists ProviderIds (ItemId GUID, Name TEXT, Value TEXT, PRIMARY KEY (ItemId, Name))",
|
"create table if not exists ProviderIds (ItemId GUID, Name TEXT, Value TEXT, PRIMARY KEY (ItemId, Name))",
|
||||||
"create index if not exists Idx_ProviderIds on ProviderIds(ItemId)",
|
"create index if not exists Idx_ProviderIds on ProviderIds(ItemId)",
|
||||||
|
|
||||||
|
"create table if not exists Images (ItemId GUID NOT NULL, Path TEXT NOT NULL, ImageType INT NOT NULL, DateModified DATETIME, IsPlaceHolder BIT NOT NULL, SortOrder INT)",
|
||||||
|
"create index if not exists idx_Images on Images(ItemId)",
|
||||||
|
|
||||||
"create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
|
"create table if not exists People (ItemId GUID, Name TEXT NOT NULL, Role TEXT, PersonType TEXT, SortOrder int, ListOrder int)",
|
||||||
"create index if not exists idxPeopleItemId on People(ItemId)",
|
"create index if not exists idxPeopleItemId on People(ItemId)",
|
||||||
"create index if not exists idxPeopleName on People(Name)",
|
"create index if not exists idxPeopleName on People(Name)",
|
||||||
@ -564,6 +571,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
_saveProviderIdsCommand.Parameters.Add(_saveProviderIdsCommand, "@Name");
|
_saveProviderIdsCommand.Parameters.Add(_saveProviderIdsCommand, "@Name");
|
||||||
_saveProviderIdsCommand.Parameters.Add(_saveProviderIdsCommand, "@Value");
|
_saveProviderIdsCommand.Parameters.Add(_saveProviderIdsCommand, "@Value");
|
||||||
|
|
||||||
|
// images
|
||||||
|
_deleteImagesCommand = _connection.CreateCommand();
|
||||||
|
_deleteImagesCommand.CommandText = "delete from Images where ItemId=@Id";
|
||||||
|
_deleteImagesCommand.Parameters.Add(_deleteImagesCommand, "@Id");
|
||||||
|
|
||||||
|
_saveImagesCommand = _connection.CreateCommand();
|
||||||
|
_saveImagesCommand.CommandText = "insert into Images (ItemId, ImageType, Path, DateModified, IsPlaceHolder, SortOrder) values (@ItemId, @ImageType, @Path, @DateModified, @IsPlaceHolder, @SortOrder)";
|
||||||
|
_saveImagesCommand.Parameters.Add(_saveImagesCommand, "@ItemId");
|
||||||
|
_saveImagesCommand.Parameters.Add(_saveImagesCommand, "@ImageType");
|
||||||
|
_saveImagesCommand.Parameters.Add(_saveImagesCommand, "@Path");
|
||||||
|
_saveImagesCommand.Parameters.Add(_saveImagesCommand, "@DateModified");
|
||||||
|
_saveImagesCommand.Parameters.Add(_saveImagesCommand, "@IsPlaceHolder");
|
||||||
|
_saveImagesCommand.Parameters.Add(_saveImagesCommand, "@SortOrder");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -878,6 +898,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
}
|
}
|
||||||
|
|
||||||
UpdateUserDataKeys(item.Id, item.GetUserDataKeys().Distinct(StringComparer.OrdinalIgnoreCase).ToList(), transaction);
|
UpdateUserDataKeys(item.Id, item.GetUserDataKeys().Distinct(StringComparer.OrdinalIgnoreCase).ToList(), transaction);
|
||||||
|
UpdateImages(item.Id, item.ImageInfos, transaction);
|
||||||
UpdateProviderIds(item.Id, item.ProviderIds, transaction);
|
UpdateProviderIds(item.Id, item.ProviderIds, transaction);
|
||||||
UpdateItemValues(item.Id, GetItemValues(item), transaction);
|
UpdateItemValues(item.Id, GetItemValues(item), transaction);
|
||||||
}
|
}
|
||||||
@ -1620,34 +1641,34 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
var item = query.SimilarTo;
|
var item = query.SimilarTo;
|
||||||
|
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
builder.Append("GetSimilarityScore(");
|
builder.Append("(");
|
||||||
|
|
||||||
builder.Append("@ItemOfficialRating,");
|
builder.Append("((OfficialRating=@ItemOfficialRating) * 10)");
|
||||||
builder.Append("OfficialRating,");
|
//builder.Append("+ ((ProductionYear=@ItemProductionYear) * 10)");
|
||||||
|
|
||||||
builder.Append("@ItemProductionYear,");
|
builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 10 Then 2 Else 0 End )");
|
||||||
builder.Append("ProductionYear,");
|
builder.Append("+(Select Case When Abs(COALESCE(ProductionYear, 0) - @ItemProductionYear) < 5 Then 2 Else 0 End )");
|
||||||
|
|
||||||
builder.Append("@ItemGenres,");
|
//// genres
|
||||||
builder.Append("Genres,");
|
builder.Append("+ ((Select count(value) from ItemValues where ItemId=Guid and Type=2 and value in (select value from itemvalues where ItemId=@SimilarItemId and type=2)) * 10)");
|
||||||
|
|
||||||
builder.Append("@ItemTags,");
|
//// tags
|
||||||
builder.Append("Tags,");
|
builder.Append("+ ((Select count(value) from ItemValues where ItemId=Guid and Type=4 and value in (select value from itemvalues where ItemId=@SimilarItemId and type=4)) * 10)");
|
||||||
|
|
||||||
builder.Append("@ItemKeywords,");
|
builder.Append("+ ((Select count(value) from ItemValues where ItemId=Guid and Type=5 and value in (select value from itemvalues where ItemId=@SimilarItemId and type=5)) * 10)");
|
||||||
builder.Append("(select group_concat((Select Value from ItemValues where ItemId=Guid and Type=5), '|')),");
|
|
||||||
|
builder.Append("+ ((Select count(value) from ItemValues where ItemId=Guid and Type=3 and value in (select value from itemvalues where ItemId=@SimilarItemId and type=3)) * 3)");
|
||||||
|
|
||||||
|
//builder.Append("+ ((Select count(Name) from People where ItemId=Guid and Name in (select Name from People where ItemId=@SimilarItemId)) * 3)");
|
||||||
|
|
||||||
|
////builder.Append("(select group_concat((Select Name from People where ItemId=Guid and Name in (Select Name from People where ItemId=@SimilarItemId)), '|'))");
|
||||||
|
|
||||||
builder.Append("@ItemStudios,");
|
|
||||||
builder.Append("Studios");
|
|
||||||
builder.Append(") as SimilarityScore");
|
builder.Append(") as SimilarityScore");
|
||||||
|
|
||||||
list.Add(builder.ToString());
|
list.Add(builder.ToString());
|
||||||
cmd.Parameters.Add(cmd, "@ItemOfficialRating", DbType.String).Value = item.OfficialRating;
|
cmd.Parameters.Add(cmd, "@ItemOfficialRating", DbType.String).Value = item.OfficialRating;
|
||||||
cmd.Parameters.Add(cmd, "@ItemProductionYear", DbType.Int32).Value = item.ProductionYear ?? -1;
|
cmd.Parameters.Add(cmd, "@ItemProductionYear", DbType.Int32).Value = item.ProductionYear ?? 0;
|
||||||
cmd.Parameters.Add(cmd, "@ItemGenres", DbType.String).Value = string.Join("|", item.Genres.ToArray());
|
cmd.Parameters.Add(cmd, "@SimilarItemId", DbType.Guid).Value = item.Id;
|
||||||
cmd.Parameters.Add(cmd, "@ItemTags", DbType.String).Value = string.Join("|", item.Tags.ToArray());
|
|
||||||
cmd.Parameters.Add(cmd, "@ItemKeywords", DbType.String).Value = string.Join("|", item.Keywords.ToArray());
|
|
||||||
cmd.Parameters.Add(cmd, "@ItemStudios", DbType.String).Value = string.Join("|", item.Studios.ToArray());
|
|
||||||
|
|
||||||
var excludeIds = query.ExcludeItemIds.ToList();
|
var excludeIds = query.ExcludeItemIds.ToList();
|
||||||
excludeIds.Add(item.Id.ToString("N"));
|
excludeIds.Add(item.Id.ToString("N"));
|
||||||
@ -1861,7 +1882,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
{
|
{
|
||||||
if (query.User != null)
|
if (query.User != null)
|
||||||
{
|
{
|
||||||
query.SortBy = new[] { "SimilarityScore", "IsUnplayed", "Random" };
|
query.SortBy = new[] { "SimilarityScore", "IsPlayed", "Random" };
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2474,6 +2495,19 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
cmd.Parameters.Add(cmd, "@NameLessThan", DbType.String).Value = query.NameLessThan.ToLower();
|
cmd.Parameters.Add(cmd, "@NameLessThan", DbType.String).Value = query.NameLessThan.ToLower();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (query.ImageTypes.Length > 0 && _config.Configuration.SchemaVersion >= 87)
|
||||||
|
{
|
||||||
|
var requiredImageIndex = 0;
|
||||||
|
|
||||||
|
foreach (var requiredImage in query.ImageTypes)
|
||||||
|
{
|
||||||
|
var paramName = "@RequiredImageType" + requiredImageIndex;
|
||||||
|
whereClauses.Add("(select path from images where ItemId=Guid and ImageType=" + paramName + " limit 1) not null");
|
||||||
|
cmd.Parameters.Add(cmd, paramName, DbType.Int32).Value = (int)requiredImage;
|
||||||
|
requiredImageIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (query.IsLiked.HasValue)
|
if (query.IsLiked.HasValue)
|
||||||
{
|
{
|
||||||
if (query.IsLiked.Value)
|
if (query.IsLiked.Value)
|
||||||
@ -2737,8 +2771,13 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
var index = 0;
|
var index = 0;
|
||||||
foreach (var pair in query.ExcludeProviderIds)
|
foreach (var pair in query.ExcludeProviderIds)
|
||||||
{
|
{
|
||||||
|
if (string.Equals(pair.Key, MetadataProviders.TmdbCollection.ToString(), StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var paramName = "@ExcludeProviderId" + index;
|
var paramName = "@ExcludeProviderId" + index;
|
||||||
excludeIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = 'Imdb'), '') <> " + paramName + ")");
|
excludeIds.Add("(COALESCE((select value from ProviderIds where ItemId=Guid and Name = '" + pair.Key + "'), '') <> " + paramName + ")");
|
||||||
cmd.Parameters.Add(cmd, paramName, DbType.String).Value = pair.Value;
|
cmd.Parameters.Add(cmd, paramName, DbType.String).Value = pair.Value;
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
@ -3179,6 +3218,11 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
_deleteProviderIdsCommand.Transaction = transaction;
|
_deleteProviderIdsCommand.Transaction = transaction;
|
||||||
_deleteProviderIdsCommand.ExecuteNonQuery();
|
_deleteProviderIdsCommand.ExecuteNonQuery();
|
||||||
|
|
||||||
|
// Delete images
|
||||||
|
_deleteImagesCommand.GetParameter(0).Value = id;
|
||||||
|
_deleteImagesCommand.Transaction = transaction;
|
||||||
|
_deleteImagesCommand.ExecuteNonQuery();
|
||||||
|
|
||||||
// Delete the item
|
// Delete the item
|
||||||
_deleteItemCommand.GetParameter(0).Value = id;
|
_deleteItemCommand.GetParameter(0).Value = id;
|
||||||
_deleteItemCommand.Transaction = transaction;
|
_deleteItemCommand.Transaction = transaction;
|
||||||
@ -3395,6 +3439,52 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void UpdateImages(Guid itemId, List<ItemImageInfo> images, IDbTransaction transaction)
|
||||||
|
{
|
||||||
|
if (itemId == Guid.Empty)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("itemId");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (images == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException("images");
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckDisposed();
|
||||||
|
|
||||||
|
// First delete
|
||||||
|
_deleteImagesCommand.GetParameter(0).Value = itemId;
|
||||||
|
_deleteImagesCommand.Transaction = transaction;
|
||||||
|
|
||||||
|
_deleteImagesCommand.ExecuteNonQuery();
|
||||||
|
|
||||||
|
var index = 0;
|
||||||
|
foreach (var image in images)
|
||||||
|
{
|
||||||
|
_saveImagesCommand.GetParameter(0).Value = itemId;
|
||||||
|
_saveImagesCommand.GetParameter(1).Value = image.Type;
|
||||||
|
_saveImagesCommand.GetParameter(2).Value = image.Path;
|
||||||
|
|
||||||
|
if (image.DateModified == default(DateTime))
|
||||||
|
{
|
||||||
|
_saveImagesCommand.GetParameter(3).Value = null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_saveImagesCommand.GetParameter(3).Value = image.DateModified;
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveImagesCommand.GetParameter(4).Value = image.IsPlaceholder;
|
||||||
|
_saveImagesCommand.GetParameter(5).Value = index;
|
||||||
|
|
||||||
|
_saveImagesCommand.Transaction = transaction;
|
||||||
|
|
||||||
|
_saveImagesCommand.ExecuteNonQuery();
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void UpdateProviderIds(Guid itemId, Dictionary<string, string> values, IDbTransaction transaction)
|
private void UpdateProviderIds(Guid itemId, Dictionary<string, string> values, IDbTransaction transaction)
|
||||||
{
|
{
|
||||||
if (itemId == Guid.Empty)
|
if (itemId == Guid.Empty)
|
||||||
@ -3404,7 +3494,7 @@ namespace MediaBrowser.Server.Implementations.Persistence
|
|||||||
|
|
||||||
if (values == null)
|
if (values == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException("keys");
|
throw new ArgumentNullException("values");
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckDisposed();
|
CheckDisposed();
|
||||||
|
@ -919,11 +919,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
|
|||||||
var val = reader.ReadElementContentAsString();
|
var val = reader.ReadElementContentAsString();
|
||||||
if (!string.IsNullOrWhiteSpace(val))
|
if (!string.IsNullOrWhiteSpace(val))
|
||||||
{
|
{
|
||||||
var hasTags = item as IHasTags;
|
item.AddTag(val);
|
||||||
if (hasTags != null)
|
|
||||||
{
|
|
||||||
hasTags.AddTag(val);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -736,19 +736,15 @@ namespace MediaBrowser.XbmcMetadata.Savers
|
|||||||
writer.WriteElementString("studio", studio);
|
writer.WriteElementString("studio", studio);
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasTags = item as IHasTags;
|
foreach (var tag in item.Tags)
|
||||||
if (hasTags != null)
|
|
||||||
{
|
{
|
||||||
foreach (var tag in hasTags.Tags)
|
if (item is MusicAlbum || item is MusicArtist)
|
||||||
{
|
{
|
||||||
if (item is MusicAlbum || item is MusicArtist)
|
writer.WriteElementString("style", tag);
|
||||||
{
|
}
|
||||||
writer.WriteElementString("style", tag);
|
else
|
||||||
}
|
{
|
||||||
else
|
writer.WriteElementString("tag", tag);
|
||||||
{
|
|
||||||
writer.WriteElementString("tag", tag);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user