2017-09-04 02:20:56 +00:00
|
|
|
using System.Collections.Generic;
|
2014-02-20 16:29:41 +00:00
|
|
|
using System.IO;
|
2013-04-15 01:41:39 +00:00
|
|
|
using System.Linq;
|
|
|
|
using NLog;
|
2014-12-02 06:26:25 +00:00
|
|
|
using NzbDrone.Common.Extensions;
|
2013-09-15 03:49:58 +00:00
|
|
|
using NzbDrone.Core.DataAugmentation.Scene;
|
|
|
|
using NzbDrone.Core.IndexerSearch.Definitions;
|
2016-09-21 03:40:53 +00:00
|
|
|
using NzbDrone.Core.MediaFiles;
|
2013-04-15 01:41:39 +00:00
|
|
|
using NzbDrone.Core.Parser.Model;
|
|
|
|
using NzbDrone.Core.Tv;
|
2017-04-28 22:05:35 +00:00
|
|
|
using NzbDrone.Core.Music;
|
|
|
|
using System;
|
2013-04-15 01:41:39 +00:00
|
|
|
|
|
|
|
namespace NzbDrone.Core.Parser
|
|
|
|
{
|
|
|
|
public interface IParsingService
|
|
|
|
{
|
2015-01-27 05:57:07 +00:00
|
|
|
LocalEpisode GetLocalEpisode(string filename, Series series);
|
|
|
|
LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource);
|
2013-04-15 01:41:39 +00:00
|
|
|
Series GetSeries(string title);
|
2017-09-13 03:28:45 +00:00
|
|
|
Artist GetArtist(string title);
|
|
|
|
Artist GetArtistFromTag(string file);
|
2015-09-26 08:45:13 +00:00
|
|
|
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
|
|
|
RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds);
|
2017-08-14 02:58:42 +00:00
|
|
|
RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null);
|
|
|
|
RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, int artistId, IEnumerable<int> albumIds);
|
2013-09-15 08:23:54 +00:00
|
|
|
List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null);
|
2017-08-14 02:58:42 +00:00
|
|
|
List<Album> GetAlbums(ParsedAlbumInfo parsedAlbumInfo, Artist artist, SearchCriteriaBase searchCriteria = null);
|
2015-09-26 08:45:13 +00:00
|
|
|
ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null);
|
2017-04-28 22:05:35 +00:00
|
|
|
|
|
|
|
// Music stuff here
|
|
|
|
LocalTrack GetLocalTrack(string filename, Artist artist);
|
2017-05-11 18:43:05 +00:00
|
|
|
LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo);
|
2017-04-28 22:05:35 +00:00
|
|
|
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public class ParsingService : IParsingService
|
|
|
|
{
|
|
|
|
private readonly IEpisodeService _episodeService;
|
|
|
|
private readonly ISeriesService _seriesService;
|
2017-04-28 22:05:35 +00:00
|
|
|
private readonly IArtistService _artistService;
|
2017-08-14 02:58:42 +00:00
|
|
|
private readonly IAlbumService _albumService;
|
2017-04-28 22:05:35 +00:00
|
|
|
private readonly ITrackService _trackService;
|
2013-04-15 01:41:39 +00:00
|
|
|
private readonly Logger _logger;
|
|
|
|
|
2013-09-14 00:41:14 +00:00
|
|
|
public ParsingService(IEpisodeService episodeService,
|
|
|
|
ISeriesService seriesService,
|
2017-05-11 18:43:05 +00:00
|
|
|
ITrackService trackService,
|
2017-08-14 02:58:42 +00:00
|
|
|
IArtistService artistService,
|
|
|
|
IAlbumService albumService,
|
2017-04-13 18:14:13 +00:00
|
|
|
// ISceneMappingService sceneMappingService,
|
2013-09-14 00:41:14 +00:00
|
|
|
Logger logger)
|
2013-04-15 01:41:39 +00:00
|
|
|
{
|
|
|
|
_episodeService = episodeService;
|
|
|
|
_seriesService = seriesService;
|
2017-08-14 02:58:42 +00:00
|
|
|
_albumService = albumService;
|
|
|
|
_artistService = artistService;
|
2017-04-13 18:14:13 +00:00
|
|
|
// _sceneMappingService = sceneMappingService;
|
2017-05-11 18:43:05 +00:00
|
|
|
_trackService = trackService;
|
2013-04-15 01:41:39 +00:00
|
|
|
_logger = logger;
|
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2015-01-27 05:57:07 +00:00
|
|
|
public LocalEpisode GetLocalEpisode(string filename, Series series)
|
2013-04-15 01:41:39 +00:00
|
|
|
{
|
2015-01-27 05:57:07 +00:00
|
|
|
return GetLocalEpisode(filename, series, null, false);
|
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2015-01-27 05:57:07 +00:00
|
|
|
public LocalEpisode GetLocalEpisode(string filename, Series series, ParsedEpisodeInfo folderInfo, bool sceneSource)
|
|
|
|
{
|
|
|
|
ParsedEpisodeInfo parsedEpisodeInfo;
|
|
|
|
|
|
|
|
if (folderInfo != null)
|
|
|
|
{
|
|
|
|
parsedEpisodeInfo = folderInfo.JsonClone();
|
|
|
|
parsedEpisodeInfo.Quality = QualityParser.ParseQuality(Path.GetFileName(filename));
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
2015-05-11 22:17:22 +00:00
|
|
|
parsedEpisodeInfo = Parser.ParsePath(filename);
|
2015-01-27 05:57:07 +00:00
|
|
|
}
|
2013-04-15 01:41:39 +00:00
|
|
|
|
2014-07-09 07:13:01 +00:00
|
|
|
if (parsedEpisodeInfo == null || parsedEpisodeInfo.IsPossibleSpecialEpisode)
|
2014-01-07 08:24:50 +00:00
|
|
|
{
|
2014-02-20 16:29:41 +00:00
|
|
|
var title = Path.GetFileNameWithoutExtension(filename);
|
2014-01-07 08:24:50 +00:00
|
|
|
var specialEpisodeInfo = ParseSpecialEpisodeTitle(title, series);
|
2014-07-09 07:13:01 +00:00
|
|
|
|
2014-01-07 08:24:50 +00:00
|
|
|
if (specialEpisodeInfo != null)
|
|
|
|
{
|
|
|
|
parsedEpisodeInfo = specialEpisodeInfo;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-04-23 06:14:55 +00:00
|
|
|
if (parsedEpisodeInfo == null)
|
2013-04-15 01:41:39 +00:00
|
|
|
{
|
2016-09-21 03:40:53 +00:00
|
|
|
if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(filename)))
|
|
|
|
{
|
|
|
|
_logger.Warn("Unable to parse episode info from path {0}", filename);
|
|
|
|
}
|
|
|
|
|
2013-04-15 01:41:39 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2013-07-23 05:50:32 +00:00
|
|
|
var episodes = GetEpisodes(parsedEpisodeInfo, series, sceneSource);
|
2013-04-15 01:41:39 +00:00
|
|
|
|
|
|
|
return new LocalEpisode
|
2013-07-23 05:50:32 +00:00
|
|
|
{
|
|
|
|
Series = series,
|
|
|
|
Quality = parsedEpisodeInfo.Quality,
|
2017-09-04 02:20:56 +00:00
|
|
|
Language = parsedEpisodeInfo.Language,
|
2013-07-23 05:50:32 +00:00
|
|
|
Episodes = episodes,
|
|
|
|
Path = filename,
|
2013-09-14 00:41:14 +00:00
|
|
|
ParsedEpisodeInfo = parsedEpisodeInfo,
|
2014-04-19 15:09:22 +00:00
|
|
|
ExistingFile = series.Path.IsParentPath(filename)
|
2013-07-23 05:50:32 +00:00
|
|
|
};
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2013-04-15 01:41:39 +00:00
|
|
|
public Series GetSeries(string title)
|
|
|
|
{
|
2013-04-23 06:14:55 +00:00
|
|
|
var parsedEpisodeInfo = Parser.ParseTitle(title);
|
2013-04-17 23:32:53 +00:00
|
|
|
|
2013-10-31 23:50:39 +00:00
|
|
|
if (parsedEpisodeInfo == null)
|
|
|
|
{
|
|
|
|
return _seriesService.FindByTitle(title);
|
|
|
|
}
|
|
|
|
|
|
|
|
var series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
|
|
|
|
|
|
|
|
if (series == null)
|
2013-04-17 23:32:53 +00:00
|
|
|
{
|
2013-10-31 23:50:39 +00:00
|
|
|
series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear,
|
|
|
|
parsedEpisodeInfo.SeriesTitleInfo.Year);
|
2013-04-17 23:32:53 +00:00
|
|
|
}
|
|
|
|
|
2013-10-31 23:50:39 +00:00
|
|
|
return series;
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
|
|
|
|
2017-09-13 03:28:45 +00:00
|
|
|
public Artist GetArtist(string title)
|
|
|
|
{
|
|
|
|
var parsedAlbumInfo = Parser.ParseAlbumTitle(title);
|
|
|
|
|
|
|
|
if (parsedAlbumInfo == null || parsedAlbumInfo.ArtistName.IsNullOrWhiteSpace())
|
|
|
|
{
|
|
|
|
return _artistService.FindByName(title);
|
|
|
|
}
|
|
|
|
|
|
|
|
return _artistService.FindByName(parsedAlbumInfo.ArtistName);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
public Artist GetArtistFromTag(string file)
|
|
|
|
{
|
|
|
|
var parsedTrackInfo = Parser.ParseMusicPath(file);
|
|
|
|
|
|
|
|
var artist = new Artist();
|
|
|
|
|
|
|
|
if (parsedTrackInfo.ArtistMBId.IsNotNullOrWhiteSpace())
|
|
|
|
{
|
|
|
|
artist = _artistService.FindById(parsedTrackInfo.ArtistMBId);
|
|
|
|
|
|
|
|
if (artist != null)
|
|
|
|
{
|
|
|
|
return artist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parsedTrackInfo == null || parsedTrackInfo.ArtistTitle.IsNullOrWhiteSpace())
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return _artistService.FindByName(parsedTrackInfo.ArtistTitle);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-08-14 02:58:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2015-09-26 08:45:13 +00:00
|
|
|
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
2013-04-15 01:41:39 +00:00
|
|
|
{
|
2013-04-28 19:46:13 +00:00
|
|
|
var remoteEpisode = new RemoteEpisode
|
|
|
|
{
|
|
|
|
ParsedEpisodeInfo = parsedEpisodeInfo,
|
|
|
|
};
|
2013-04-28 00:25:28 +00:00
|
|
|
|
2015-09-26 08:45:13 +00:00
|
|
|
var series = GetSeries(parsedEpisodeInfo, tvdbId, tvRageId, searchCriteria);
|
2013-09-15 03:49:58 +00:00
|
|
|
|
|
|
|
if (series == null)
|
|
|
|
{
|
|
|
|
return remoteEpisode;
|
|
|
|
}
|
|
|
|
|
|
|
|
remoteEpisode.Series = series;
|
|
|
|
remoteEpisode.Episodes = GetEpisodes(parsedEpisodeInfo, series, true, searchCriteria);
|
|
|
|
|
|
|
|
return remoteEpisode;
|
|
|
|
}
|
|
|
|
|
2017-08-14 02:58:42 +00:00
|
|
|
public RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria = null)
|
|
|
|
{
|
|
|
|
var remoteAlbum = new RemoteAlbum
|
|
|
|
{
|
|
|
|
ParsedAlbumInfo = parsedAlbumInfo,
|
|
|
|
};
|
|
|
|
|
|
|
|
var artist = GetArtist(parsedAlbumInfo, searchCriteria);
|
|
|
|
|
|
|
|
if (artist == null)
|
|
|
|
{
|
|
|
|
return remoteAlbum;
|
|
|
|
}
|
|
|
|
|
|
|
|
remoteAlbum.Artist = artist;
|
|
|
|
remoteAlbum.Albums = GetAlbums(parsedAlbumInfo, artist, searchCriteria);
|
|
|
|
|
|
|
|
return remoteAlbum;
|
|
|
|
}
|
|
|
|
|
|
|
|
public List<Album> GetAlbums(ParsedAlbumInfo parsedAlbumInfo, Artist artist, SearchCriteriaBase searchCriteria = null)
|
|
|
|
{
|
|
|
|
var albumTitle = parsedAlbumInfo.AlbumTitle;
|
|
|
|
var result = new List<Album>();
|
|
|
|
|
|
|
|
if (parsedAlbumInfo.AlbumTitle == null)
|
|
|
|
{
|
|
|
|
return new List<Album>();
|
|
|
|
}
|
|
|
|
|
|
|
|
Album albumInfo = null;
|
|
|
|
|
|
|
|
if (searchCriteria != null)
|
|
|
|
{
|
|
|
|
albumInfo = searchCriteria.Albums.SingleOrDefault(e => e.Title == albumTitle);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (albumInfo == null)
|
|
|
|
{
|
|
|
|
albumInfo = _albumService.FindByTitle(artist.Id, parsedAlbumInfo.AlbumTitle);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (albumInfo != null)
|
|
|
|
{
|
|
|
|
result.Add(albumInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_logger.Debug("Unable to find {0}", parsedAlbumInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2015-01-19 23:53:31 +00:00
|
|
|
public RemoteEpisode Map(ParsedEpisodeInfo parsedEpisodeInfo, int seriesId, IEnumerable<int> episodeIds)
|
|
|
|
{
|
|
|
|
return new RemoteEpisode
|
2017-08-14 02:58:42 +00:00
|
|
|
{
|
|
|
|
ParsedEpisodeInfo = parsedEpisodeInfo,
|
|
|
|
Series = _seriesService.GetSeries(seriesId),
|
|
|
|
Episodes = _episodeService.GetEpisodes(episodeIds)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public RemoteAlbum Map(ParsedAlbumInfo parsedAlbumInfo, int artistId, IEnumerable<int> albumIds)
|
|
|
|
{
|
|
|
|
return new RemoteAlbum
|
|
|
|
{
|
|
|
|
ParsedAlbumInfo = parsedAlbumInfo,
|
|
|
|
Artist = _artistService.GetArtist(artistId),
|
|
|
|
Albums = _albumService.GetAlbums(albumIds)
|
|
|
|
};
|
2015-01-19 23:53:31 +00:00
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2013-09-15 08:23:54 +00:00
|
|
|
public List<Episode> GetEpisodes(ParsedEpisodeInfo parsedEpisodeInfo, Series series, bool sceneSource, SearchCriteriaBase searchCriteria = null)
|
2013-04-15 01:41:39 +00:00
|
|
|
{
|
2013-12-23 01:32:50 +00:00
|
|
|
if (parsedEpisodeInfo.FullSeason)
|
|
|
|
{
|
|
|
|
return _episodeService.GetEpisodesBySeason(series.Id, parsedEpisodeInfo.SeasonNumber);
|
|
|
|
}
|
|
|
|
|
2014-07-09 07:13:01 +00:00
|
|
|
if (parsedEpisodeInfo.IsDaily)
|
2013-04-15 01:41:39 +00:00
|
|
|
{
|
|
|
|
if (series.SeriesType == SeriesTypes.Standard)
|
|
|
|
{
|
2013-07-05 05:17:25 +00:00
|
|
|
_logger.Warn("Found daily-style episode for non-daily series: {0}.", series);
|
2016-03-27 21:17:56 +00:00
|
|
|
return new List<Episode>();
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
2013-09-15 03:49:58 +00:00
|
|
|
|
2013-10-23 05:17:02 +00:00
|
|
|
var episodeInfo = GetDailyEpisode(series, parsedEpisodeInfo.AirDate, searchCriteria);
|
2013-04-15 01:41:39 +00:00
|
|
|
|
|
|
|
if (episodeInfo != null)
|
|
|
|
{
|
2016-03-27 21:17:56 +00:00
|
|
|
return new List<Episode> { episodeInfo };
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
|
|
|
|
2016-03-27 21:17:56 +00:00
|
|
|
return new List<Episode>();
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
|
|
|
|
2014-07-09 07:13:01 +00:00
|
|
|
if (parsedEpisodeInfo.IsAbsoluteNumbering)
|
2013-11-08 00:24:09 +00:00
|
|
|
{
|
2016-03-27 21:17:56 +00:00
|
|
|
return GetAnimeEpisodes(series, parsedEpisodeInfo, sceneSource);
|
2013-11-08 00:24:09 +00:00
|
|
|
}
|
|
|
|
|
2016-03-27 21:17:56 +00:00
|
|
|
return GetStandardEpisodes(series, parsedEpisodeInfo, sceneSource, searchCriteria);
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
2013-09-15 03:49:58 +00:00
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2015-09-26 08:45:13 +00:00
|
|
|
public ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria = null)
|
2014-02-20 16:29:41 +00:00
|
|
|
{
|
|
|
|
if (searchCriteria != null)
|
|
|
|
{
|
2015-09-26 08:45:13 +00:00
|
|
|
if (tvdbId == 0)
|
2017-04-13 18:14:13 +00:00
|
|
|
tvdbId = 0; // _sceneMappingService.FindTvdbId(title) ?? 0;
|
2015-09-26 08:45:13 +00:00
|
|
|
|
|
|
|
if (tvdbId != 0 && tvdbId == searchCriteria.Series.TvdbId)
|
2014-02-20 16:29:41 +00:00
|
|
|
{
|
2015-09-26 08:45:13 +00:00
|
|
|
return ParseSpecialEpisodeTitle(title, searchCriteria.Series);
|
2014-02-20 16:29:41 +00:00
|
|
|
}
|
|
|
|
|
2015-09-26 08:45:13 +00:00
|
|
|
if (tvRageId != 0 && tvRageId == searchCriteria.Series.TvRageId)
|
2014-02-20 16:29:41 +00:00
|
|
|
{
|
|
|
|
return ParseSpecialEpisodeTitle(title, searchCriteria.Series);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-03 00:08:53 +00:00
|
|
|
var series = GetSeries(title);
|
2016-01-06 06:56:34 +00:00
|
|
|
|
2015-03-03 00:08:53 +00:00
|
|
|
if (series == null)
|
|
|
|
{
|
|
|
|
series = _seriesService.FindByTitleInexact(title);
|
|
|
|
}
|
2016-01-06 06:56:34 +00:00
|
|
|
|
2015-09-26 08:45:13 +00:00
|
|
|
if (series == null && tvdbId > 0)
|
|
|
|
{
|
|
|
|
series = _seriesService.FindByTvdbId(tvdbId);
|
|
|
|
}
|
2016-01-06 06:56:34 +00:00
|
|
|
|
2014-02-20 16:29:41 +00:00
|
|
|
if (series == null && tvRageId > 0)
|
|
|
|
{
|
|
|
|
series = _seriesService.FindByTvRageId(tvRageId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (series == null)
|
|
|
|
{
|
2014-03-13 20:12:42 +00:00
|
|
|
_logger.Debug("No matching series {0}", title);
|
2014-02-20 16:29:41 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ParseSpecialEpisodeTitle(title, series);
|
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2014-10-03 23:29:52 +00:00
|
|
|
private ParsedEpisodeInfo ParseSpecialEpisodeTitle(string title, Series series)
|
2014-02-20 16:29:41 +00:00
|
|
|
{
|
|
|
|
// find special episode in series season 0
|
2014-12-15 18:52:16 +00:00
|
|
|
var episode = _episodeService.FindEpisodeByTitle(series.Id, 0, title);
|
2014-07-09 07:13:01 +00:00
|
|
|
|
2014-02-20 16:29:41 +00:00
|
|
|
if (episode != null)
|
|
|
|
{
|
|
|
|
// create parsed info from tv episode
|
|
|
|
var info = new ParsedEpisodeInfo();
|
|
|
|
info.SeriesTitle = series.Title;
|
|
|
|
info.SeriesTitleInfo = new SeriesTitleInfo();
|
|
|
|
info.SeriesTitleInfo.Title = info.SeriesTitle;
|
|
|
|
info.SeasonNumber = episode.SeasonNumber;
|
|
|
|
info.EpisodeNumbers = new int[1] { episode.EpisodeNumber };
|
|
|
|
info.FullSeason = false;
|
|
|
|
info.Quality = QualityParser.ParseQuality(title);
|
|
|
|
info.ReleaseGroup = Parser.ParseReleaseGroup(title);
|
2015-12-25 09:22:00 +00:00
|
|
|
info.Language = LanguageParser.ParseLanguage(title);
|
2014-12-15 18:52:16 +00:00
|
|
|
info.Special = true;
|
2014-02-20 16:29:41 +00:00
|
|
|
|
2015-03-01 21:34:39 +00:00
|
|
|
_logger.Debug("Found special episode {0} for title '{1}'", info, title);
|
2014-02-20 16:29:41 +00:00
|
|
|
return info;
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-08-14 02:58:42 +00:00
|
|
|
private Artist GetArtist(ParsedAlbumInfo parsedAlbumInfo, SearchCriteriaBase searchCriteria)
|
|
|
|
{
|
|
|
|
Artist artist = null;
|
|
|
|
|
|
|
|
if (searchCriteria != null)
|
|
|
|
{
|
|
|
|
if (searchCriteria.Artist.CleanName == parsedAlbumInfo.ArtistName.CleanSeriesTitle())
|
|
|
|
{
|
|
|
|
return searchCriteria.Artist;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
artist = _artistService.FindByName(parsedAlbumInfo.ArtistName);
|
|
|
|
|
|
|
|
if (artist == null)
|
|
|
|
{
|
|
|
|
_logger.Debug("No matching series {0}", parsedAlbumInfo.ArtistName);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return artist;
|
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2015-09-26 08:45:13 +00:00
|
|
|
private Series GetSeries(ParsedEpisodeInfo parsedEpisodeInfo, int tvdbId, int tvRageId, SearchCriteriaBase searchCriteria)
|
2013-11-27 07:48:57 +00:00
|
|
|
{
|
2015-08-03 17:19:59 +00:00
|
|
|
Series series = null;
|
|
|
|
|
2017-04-13 18:14:13 +00:00
|
|
|
//var sceneMappingTvdbId = _sceneMappingService.FindTvdbId(parsedEpisodeInfo.SeriesTitle);
|
|
|
|
//if (sceneMappingTvdbId.HasValue)
|
|
|
|
//{
|
|
|
|
// if (searchCriteria != null && searchCriteria.Series.TvdbId == sceneMappingTvdbId.Value)
|
|
|
|
// {
|
|
|
|
// return searchCriteria.Series;
|
|
|
|
// }
|
2013-09-15 08:23:54 +00:00
|
|
|
|
2017-04-13 18:14:13 +00:00
|
|
|
// series = _seriesService.FindByTvdbId(sceneMappingTvdbId.Value);
|
2015-08-03 17:19:59 +00:00
|
|
|
|
2017-04-13 18:14:13 +00:00
|
|
|
// if (series == null)
|
|
|
|
// {
|
|
|
|
// _logger.Debug("No matching series {0}", parsedEpisodeInfo.SeriesTitle);
|
|
|
|
// return null;
|
|
|
|
// }
|
2015-08-03 17:19:59 +00:00
|
|
|
|
2017-04-13 18:14:13 +00:00
|
|
|
// return series;
|
|
|
|
//}
|
2013-09-15 08:23:54 +00:00
|
|
|
|
2015-08-03 17:19:59 +00:00
|
|
|
if (searchCriteria != null)
|
2013-09-15 08:23:54 +00:00
|
|
|
{
|
2015-08-03 17:19:59 +00:00
|
|
|
if (searchCriteria.Series.CleanTitle == parsedEpisodeInfo.SeriesTitle.CleanSeriesTitle())
|
|
|
|
{
|
|
|
|
return searchCriteria.Series;
|
|
|
|
}
|
2013-09-15 08:23:54 +00:00
|
|
|
|
2015-09-26 08:45:13 +00:00
|
|
|
if (tvdbId > 0 && tvdbId == searchCriteria.Series.TvdbId)
|
|
|
|
{
|
|
|
|
//TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import
|
|
|
|
return searchCriteria.Series;
|
|
|
|
}
|
|
|
|
|
2015-08-03 17:19:59 +00:00
|
|
|
if (tvRageId > 0 && tvRageId == searchCriteria.Series.TvRageId)
|
|
|
|
{
|
|
|
|
//TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import
|
|
|
|
return searchCriteria.Series;
|
|
|
|
}
|
|
|
|
}
|
2013-09-15 08:23:54 +00:00
|
|
|
|
2015-08-03 17:19:59 +00:00
|
|
|
series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitle);
|
2013-09-15 08:23:54 +00:00
|
|
|
|
2017-03-19 05:30:51 +00:00
|
|
|
if (series == null && parsedEpisodeInfo.SeriesTitleInfo.Year > 0)
|
|
|
|
{
|
|
|
|
series = _seriesService.FindByTitle(parsedEpisodeInfo.SeriesTitleInfo.TitleWithoutYear, parsedEpisodeInfo.SeriesTitleInfo.Year);
|
|
|
|
}
|
|
|
|
|
2015-09-26 08:45:13 +00:00
|
|
|
if (series == null && tvdbId > 0)
|
|
|
|
{
|
|
|
|
//TODO: If series is found by TvdbId, we should report it as a scene naming exception, since it will fail to import
|
|
|
|
series = _seriesService.FindByTvdbId(tvdbId);
|
|
|
|
}
|
|
|
|
|
2013-09-15 08:23:54 +00:00
|
|
|
if (series == null && tvRageId > 0)
|
|
|
|
{
|
|
|
|
//TODO: If series is found by TvRageId, we should report it as a scene naming exception, since it will fail to import
|
|
|
|
series = _seriesService.FindByTvRageId(tvRageId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (series == null)
|
|
|
|
{
|
2014-03-13 20:12:42 +00:00
|
|
|
_logger.Debug("No matching series {0}", parsedEpisodeInfo.SeriesTitle);
|
2013-09-15 08:23:54 +00:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
return series;
|
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2015-10-03 17:45:26 +00:00
|
|
|
private Episode GetDailyEpisode(Series series, string airDate, SearchCriteriaBase searchCriteria)
|
2013-09-15 03:49:58 +00:00
|
|
|
{
|
|
|
|
Episode episodeInfo = null;
|
|
|
|
|
|
|
|
if (searchCriteria != null)
|
|
|
|
{
|
|
|
|
episodeInfo = searchCriteria.Episodes.SingleOrDefault(
|
2013-10-23 05:17:02 +00:00
|
|
|
e => e.AirDate == airDate);
|
2013-09-15 03:49:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (episodeInfo == null)
|
|
|
|
{
|
2013-09-15 08:23:54 +00:00
|
|
|
episodeInfo = _episodeService.FindEpisode(series.Id, airDate);
|
2013-09-15 03:49:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return episodeInfo;
|
|
|
|
}
|
2016-03-27 21:17:56 +00:00
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2016-03-27 21:17:56 +00:00
|
|
|
private List<Episode> GetAnimeEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource)
|
|
|
|
{
|
|
|
|
var result = new List<Episode>();
|
|
|
|
|
2017-04-13 18:14:13 +00:00
|
|
|
// var sceneSeasonNumber = _sceneMappingService.GetSceneSeasonNumber(parsedEpisodeInfo.SeriesTitle);
|
2016-03-27 21:17:56 +00:00
|
|
|
|
|
|
|
foreach (var absoluteEpisodeNumber in parsedEpisodeInfo.AbsoluteEpisodeNumbers)
|
|
|
|
{
|
|
|
|
Episode episode = null;
|
|
|
|
|
|
|
|
if (parsedEpisodeInfo.Special)
|
|
|
|
{
|
|
|
|
episode = _episodeService.FindEpisode(series.Id, 0, absoluteEpisodeNumber);
|
|
|
|
}
|
|
|
|
|
2017-04-13 18:14:13 +00:00
|
|
|
//else if (sceneSource)
|
|
|
|
//{
|
|
|
|
// // Is there a reason why we excluded season 1 from this handling before?
|
|
|
|
// // Might have something to do with the scene name to season number check
|
|
|
|
// // If this needs to be reverted tests will need to be added
|
|
|
|
// if (sceneSeasonNumber.HasValue)
|
|
|
|
// {
|
|
|
|
// var episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber);
|
|
|
|
|
|
|
|
// if (episodes.Count == 1)
|
|
|
|
// {
|
|
|
|
// episode = episodes.First();
|
|
|
|
// }
|
|
|
|
|
|
|
|
// if (episode == null)
|
|
|
|
// {
|
|
|
|
// episode = _episodeService.FindEpisode(series.Id, sceneSeasonNumber.Value, absoluteEpisodeNumber);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
|
|
|
|
// else
|
|
|
|
// {
|
|
|
|
// episode = _episodeService.FindEpisodeBySceneNumbering(series.Id, absoluteEpisodeNumber);
|
|
|
|
// }
|
|
|
|
//}
|
2016-03-27 21:17:56 +00:00
|
|
|
|
|
|
|
if (episode == null)
|
|
|
|
{
|
|
|
|
episode = _episodeService.FindEpisode(series.Id, absoluteEpisodeNumber);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (episode != null)
|
|
|
|
{
|
|
|
|
_logger.Debug("Using absolute episode number {0} for: {1} - TVDB: {2}x{3:00}",
|
|
|
|
absoluteEpisodeNumber,
|
|
|
|
series.Title,
|
|
|
|
episode.SeasonNumber,
|
|
|
|
episode.EpisodeNumber);
|
|
|
|
|
|
|
|
result.Add(episode);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
//private List<Track> GetStandardTracks(Artist artist, ParsedTrackInfo parsedTrackInfo, SearchCriteriaBase searchCriteria)
|
|
|
|
//{
|
|
|
|
// var result = new List<Track>();
|
2017-05-11 18:43:05 +00:00
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
// if (parsedTrackInfo.TrackNumbers == null)
|
|
|
|
// {
|
|
|
|
// return result;
|
|
|
|
// }
|
2017-05-11 18:43:05 +00:00
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
// foreach (var trackNumber in parsedTrackInfo.TrackNumbers)
|
|
|
|
// {
|
|
|
|
// Track trackInfo = null;
|
2017-05-11 18:43:05 +00:00
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
// if (searchCriteria != null)
|
|
|
|
// {
|
|
|
|
// trackInfo = searchCriteria.Tracks.SingleOrDefault(e => e.TrackNumber == trackNumber); //e => e.SeasonNumber == seasonNumber && e.TrackNumber == trackNumber
|
|
|
|
// }
|
2017-05-11 18:43:05 +00:00
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
// if (trackInfo == null)
|
|
|
|
// {
|
|
|
|
// // TODO: [ParsingService]: FindTrack by artistID and trackNumber (or albumID and trackNumber if we change db schema to album as base)
|
|
|
|
// _logger.Debug("TrackInfo is null, we will not add as FindTrack(artistId, trackNumber) is not implemented");
|
|
|
|
// //trackInfo = _trackService.FindTrack(artist.SpotifyId, trackNumber);
|
|
|
|
// }
|
2017-05-11 18:43:05 +00:00
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
// if (trackInfo != null)
|
|
|
|
// {
|
|
|
|
// result.Add(trackInfo);
|
|
|
|
// }
|
2017-05-11 18:43:05 +00:00
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
// else
|
|
|
|
// {
|
|
|
|
// _logger.Debug("Unable to find {0}", parsedTrackInfo);
|
|
|
|
// }
|
|
|
|
// }
|
2017-05-11 18:43:05 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
// return result;
|
|
|
|
//}
|
2017-05-11 18:43:05 +00:00
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
[System.Obsolete("Used for sonarr, not lidarr")]
|
2016-03-27 21:17:56 +00:00
|
|
|
private List<Episode> GetStandardEpisodes(Series series, ParsedEpisodeInfo parsedEpisodeInfo, bool sceneSource, SearchCriteriaBase searchCriteria)
|
|
|
|
{
|
|
|
|
var result = new List<Episode>();
|
|
|
|
var seasonNumber = parsedEpisodeInfo.SeasonNumber;
|
|
|
|
|
2017-04-13 18:14:13 +00:00
|
|
|
//if (sceneSource)
|
|
|
|
//{
|
|
|
|
// var sceneMapping = _sceneMappingService.FindSceneMapping(parsedEpisodeInfo.SeriesTitle);
|
2016-03-27 21:17:56 +00:00
|
|
|
|
2017-04-13 18:14:13 +00:00
|
|
|
// if (sceneMapping != null && sceneMapping.SeasonNumber.HasValue && sceneMapping.SeasonNumber.Value >= 0 &&
|
|
|
|
// sceneMapping.SceneSeasonNumber == seasonNumber)
|
|
|
|
// {
|
|
|
|
// seasonNumber = sceneMapping.SeasonNumber.Value;
|
|
|
|
// }
|
|
|
|
//}
|
2016-03-27 21:17:56 +00:00
|
|
|
|
|
|
|
if (parsedEpisodeInfo.EpisodeNumbers == null)
|
|
|
|
{
|
|
|
|
return new List<Episode>();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (var episodeNumber in parsedEpisodeInfo.EpisodeNumbers)
|
|
|
|
{
|
|
|
|
if (series.UseSceneNumbering && sceneSource)
|
|
|
|
{
|
|
|
|
List<Episode> episodes = new List<Episode>();
|
|
|
|
|
|
|
|
if (searchCriteria != null)
|
|
|
|
{
|
|
|
|
episodes = searchCriteria.Episodes.Where(e => e.SceneSeasonNumber == parsedEpisodeInfo.SeasonNumber &&
|
|
|
|
e.SceneEpisodeNumber == episodeNumber).ToList();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!episodes.Any())
|
|
|
|
{
|
|
|
|
episodes = _episodeService.FindEpisodesBySceneNumbering(series.Id, seasonNumber, episodeNumber);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (episodes != null && episodes.Any())
|
|
|
|
{
|
|
|
|
_logger.Debug("Using Scene to TVDB Mapping for: {0} - Scene: {1}x{2:00} - TVDB: {3}",
|
|
|
|
series.Title,
|
|
|
|
episodes.First().SceneSeasonNumber,
|
|
|
|
episodes.First().SceneEpisodeNumber,
|
|
|
|
string.Join(", ", episodes.Select(e => string.Format("{0}x{1:00}", e.SeasonNumber, e.EpisodeNumber))));
|
|
|
|
|
|
|
|
result.AddRange(episodes);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Episode episodeInfo = null;
|
|
|
|
|
|
|
|
if (searchCriteria != null)
|
|
|
|
{
|
|
|
|
episodeInfo = searchCriteria.Episodes.SingleOrDefault(e => e.SeasonNumber == seasonNumber && e.EpisodeNumber == episodeNumber);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (episodeInfo == null)
|
|
|
|
{
|
|
|
|
episodeInfo = _episodeService.FindEpisode(series.Id, seasonNumber, episodeNumber);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (episodeInfo != null)
|
|
|
|
{
|
|
|
|
result.Add(episodeInfo);
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_logger.Debug("Unable to find {0}", parsedEpisodeInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
2017-04-28 22:05:35 +00:00
|
|
|
|
2017-05-29 17:09:18 +00:00
|
|
|
|
2017-04-28 22:05:35 +00:00
|
|
|
public LocalTrack GetLocalTrack(string filename, Artist artist)
|
|
|
|
{
|
2017-05-11 18:43:05 +00:00
|
|
|
return GetLocalTrack(filename, artist, null);
|
2017-04-28 22:05:35 +00:00
|
|
|
}
|
|
|
|
|
2017-05-11 18:43:05 +00:00
|
|
|
public LocalTrack GetLocalTrack(string filename, Artist artist, ParsedTrackInfo folderInfo)
|
2017-04-28 22:05:35 +00:00
|
|
|
{
|
|
|
|
ParsedTrackInfo parsedTrackInfo;
|
|
|
|
|
2017-07-03 18:39:06 +00:00
|
|
|
|
2017-04-28 22:05:35 +00:00
|
|
|
if (folderInfo != null)
|
|
|
|
{
|
|
|
|
parsedTrackInfo = folderInfo.JsonClone();
|
|
|
|
parsedTrackInfo.Quality = QualityParser.ParseQuality(Path.GetFileName(filename));
|
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
parsedTrackInfo = Parser.ParseMusicPath(filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (parsedTrackInfo == null)
|
|
|
|
{
|
|
|
|
if (MediaFileExtensions.Extensions.Contains(Path.GetExtension(filename)))
|
|
|
|
{
|
|
|
|
_logger.Warn("Unable to parse track info from path {0}", filename);
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
var tracks = GetTracks(artist, parsedTrackInfo);
|
2017-09-13 03:28:45 +00:00
|
|
|
var album = _albumService.FindByTitle(artist.Id, parsedTrackInfo.AlbumTitle);
|
2017-04-28 22:05:35 +00:00
|
|
|
|
|
|
|
return new LocalTrack
|
|
|
|
{
|
|
|
|
Artist = artist,
|
2017-09-13 03:28:45 +00:00
|
|
|
Album = album,
|
2017-04-28 22:05:35 +00:00
|
|
|
Quality = parsedTrackInfo.Quality,
|
2017-09-04 02:20:56 +00:00
|
|
|
Language = parsedTrackInfo.Language,
|
2017-04-28 22:05:35 +00:00
|
|
|
Tracks = tracks,
|
|
|
|
Path = filename,
|
|
|
|
ParsedTrackInfo = parsedTrackInfo,
|
|
|
|
ExistingFile = artist.Path.IsParentPath(filename)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
private List<Track> GetTracks(Artist artist, ParsedTrackInfo parsedTrackInfo)
|
2017-05-29 17:09:18 +00:00
|
|
|
{
|
|
|
|
var result = new List<Track>();
|
|
|
|
|
2017-09-15 03:33:13 +00:00
|
|
|
if (parsedTrackInfo.AlbumTitle.IsNullOrWhiteSpace())
|
|
|
|
{
|
2017-09-29 03:32:42 +00:00
|
|
|
_logger.Debug("Album title could not be parsed for {0}", parsedTrackInfo);
|
2017-09-15 03:33:13 +00:00
|
|
|
return new List<Track>();
|
|
|
|
}
|
|
|
|
|
|
|
|
var album = _albumService.FindByTitle(artist.Id, parsedTrackInfo.AlbumTitle);
|
2017-09-29 03:32:42 +00:00
|
|
|
_logger.Debug("Album {0} selected for {1}", album, parsedTrackInfo);
|
2017-09-15 03:33:13 +00:00
|
|
|
|
|
|
|
if (album == null)
|
|
|
|
{
|
2017-09-29 03:32:42 +00:00
|
|
|
_logger.Debug("Parsed album title not found in Db for {0}", parsedTrackInfo);
|
2017-09-15 03:33:13 +00:00
|
|
|
return new List<Track>();
|
|
|
|
}
|
|
|
|
|
|
|
|
Track trackInfo = null;
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
if (parsedTrackInfo.Title.IsNotNullOrWhiteSpace())
|
2017-09-15 03:33:13 +00:00
|
|
|
{
|
2017-09-29 03:32:42 +00:00
|
|
|
trackInfo = _trackService.FindTrackByTitle(artist.Id, album.Id, parsedTrackInfo.Title);
|
|
|
|
_logger.Debug("Track {0} selected for {1}", trackInfo, parsedTrackInfo);
|
|
|
|
|
|
|
|
if (trackInfo != null)
|
|
|
|
{
|
|
|
|
result.Add(trackInfo);
|
|
|
|
return result;
|
|
|
|
}
|
2017-09-15 03:33:13 +00:00
|
|
|
}
|
|
|
|
|
2017-09-29 03:32:42 +00:00
|
|
|
_logger.Debug("Track title search unsuccessful, falling back to track number for {1}", trackInfo, parsedTrackInfo);
|
|
|
|
|
2017-05-29 17:09:18 +00:00
|
|
|
if (parsedTrackInfo.TrackNumbers == null)
|
|
|
|
{
|
2017-09-29 03:32:42 +00:00
|
|
|
_logger.Debug("Track has no track numbers: {1}", trackInfo, parsedTrackInfo);
|
2017-05-29 17:09:18 +00:00
|
|
|
return new List<Track>();
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (var trackNumber in parsedTrackInfo.TrackNumbers)
|
|
|
|
{
|
2017-09-15 03:33:13 +00:00
|
|
|
Track trackInfoByNumber = null;
|
2017-05-29 17:09:18 +00:00
|
|
|
|
2017-09-15 03:33:13 +00:00
|
|
|
trackInfoByNumber = _trackService.FindTrack(artist.Id, album.Id, trackNumber);
|
2017-09-29 03:32:42 +00:00
|
|
|
_logger.Debug("Track {0} selected for {1}", trackInfoByNumber, parsedTrackInfo);
|
2017-05-29 17:09:18 +00:00
|
|
|
|
2017-09-15 03:33:13 +00:00
|
|
|
if (trackInfoByNumber != null)
|
2017-05-29 17:09:18 +00:00
|
|
|
{
|
2017-09-15 03:33:13 +00:00
|
|
|
result.Add(trackInfoByNumber);
|
2017-05-29 17:09:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_logger.Debug("Unable to find {0}", parsedTrackInfo);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
2017-04-28 22:05:35 +00:00
|
|
|
}
|
2013-04-15 01:41:39 +00:00
|
|
|
}
|
2017-07-03 18:39:06 +00:00
|
|
|
}
|