diff options
21 files changed, 382 insertions, 292 deletions
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj index 4a32d00a2..e3d404644 100644 --- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj +++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj @@ -51,6 +51,15 @@ <Compile Include="Collections\CollectionManager.cs" /> <Compile Include="Devices\DeviceManager.cs" /> <Compile Include="Dto\DtoService.cs" /> + <Compile Include="EntryPoints\AutomaticRestartEntryPoint.cs" /> + <Compile Include="EntryPoints\LibraryChangedNotifier.cs" /> + <Compile Include="EntryPoints\LoadRegistrations.cs" /> + <Compile Include="EntryPoints\RecordingNotifier.cs" /> + <Compile Include="EntryPoints\RefreshUsersMetadata.cs" /> + <Compile Include="EntryPoints\ServerEventNotifier.cs" /> + <Compile Include="EntryPoints\UsageEntryPoint.cs" /> + <Compile Include="EntryPoints\UsageReporter.cs" /> + <Compile Include="EntryPoints\UserDataChangeNotifier.cs" /> <Compile Include="FileOrganization\EpisodeFileOrganizer.cs" /> <Compile Include="FileOrganization\Extensions.cs" /> <Compile Include="FileOrganization\FileOrganizationNotifier.cs" /> @@ -100,6 +109,8 @@ <Compile Include="Library\Validators\StudiosValidator.cs" /> <Compile Include="Library\Validators\YearsPostScanTask.cs" /> <Compile Include="Logging\PatternsLogger.cs" /> + <Compile Include="MediaEncoder\EncodingManager.cs" /> + <Compile Include="News\NewsEntryPoint.cs" /> <Compile Include="News\NewsService.cs" /> <Compile Include="Notifications\CoreNotificationTypes.cs" /> <Compile Include="Notifications\IConfigurableNotificationService.cs" /> diff --git a/MediaBrowser.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs index 1067e052d..38708648f 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/AutomaticRestartEntryPoint.cs @@ -10,8 +10,9 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.LiveTv; using MediaBrowser.Model.LiveTv; +using MediaBrowser.Model.Threading; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { public class AutomaticRestartEntryPoint : IServerEntryPoint { @@ -21,10 +22,11 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly ISessionManager _sessionManager; private readonly IServerConfigurationManager _config; private readonly ILiveTvManager _liveTvManager; + private readonly ITimerFactory _timerFactory; - private Timer _timer; + private ITimer _timer; - public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager) + public AutomaticRestartEntryPoint(IServerApplicationHost appHost, ILogger logger, ITaskManager iTaskManager, ISessionManager sessionManager, IServerConfigurationManager config, ILiveTvManager liveTvManager, ITimerFactory timerFactory) { _appHost = appHost; _logger = logger; @@ -32,6 +34,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _sessionManager = sessionManager; _config = config; _liveTvManager = liveTvManager; + _timerFactory = timerFactory; } public void Run() @@ -48,7 +51,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints if (_appHost.HasPendingRestart) { - _timer = new Timer(TimerCallback, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); + _timer = _timerFactory.Create(TimerCallback, null, TimeSpan.FromMinutes(10), TimeSpan.FromMinutes(10)); } } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs index 9f06a9860..91142f928 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs @@ -10,8 +10,9 @@ using System.Linq; using System.Threading; using MediaBrowser.Controller.Entities.Audio; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Threading; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { public class LibraryChangedNotifier : IServerEntryPoint { @@ -23,6 +24,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly ISessionManager _sessionManager; private readonly IUserManager _userManager; private readonly ILogger _logger; + private readonly ITimerFactory _timerFactory; /// <summary> /// The _library changed sync lock @@ -40,19 +42,20 @@ namespace MediaBrowser.Server.Implementations.EntryPoints /// Gets or sets the library update timer. /// </summary> /// <value>The library update timer.</value> - private Timer LibraryUpdateTimer { get; set; } + private ITimer LibraryUpdateTimer { get; set; } /// <summary> /// The library update duration /// </summary> private const int LibraryUpdateDuration = 5000; - public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger) + public LibraryChangedNotifier(ILibraryManager libraryManager, ISessionManager sessionManager, IUserManager userManager, ILogger logger, ITimerFactory timerFactory) { _libraryManager = libraryManager; _sessionManager = sessionManager; _userManager = userManager; _logger = logger; + _timerFactory = timerFactory; } public void Run() @@ -79,7 +82,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { if (LibraryUpdateTimer == null) { - LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); } else @@ -112,7 +115,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { if (LibraryUpdateTimer == null) { - LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); } else @@ -140,7 +143,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { if (LibraryUpdateTimer == null) { - LibraryUpdateTimer = new Timer(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, + LibraryUpdateTimer = _timerFactory.Create(LibraryUpdateTimerCallback, null, LibraryUpdateDuration, Timeout.Infinite); } else diff --git a/MediaBrowser.Server.Implementations/EntryPoints/LoadRegistrations.cs b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs index 47f22cae6..0203b5192 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/LoadRegistrations.cs +++ b/Emby.Server.Implementations/EntryPoints/LoadRegistrations.cs @@ -3,9 +3,9 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Model.Logging; using System; using System.Threading.Tasks; -using MediaBrowser.Server.Implementations.Threading; +using MediaBrowser.Model.Threading; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { /// <summary> /// Class LoadRegistrations @@ -22,16 +22,18 @@ namespace MediaBrowser.Server.Implementations.EntryPoints /// </summary> private readonly ILogger _logger; - private PeriodicTimer _timer; + private ITimer _timer; + private readonly ITimerFactory _timerFactory; /// <summary> /// Initializes a new instance of the <see cref="LoadRegistrations" /> class. /// </summary> /// <param name="securityManager">The security manager.</param> /// <param name="logManager">The log manager.</param> - public LoadRegistrations(ISecurityManager securityManager, ILogManager logManager) + public LoadRegistrations(ISecurityManager securityManager, ILogManager logManager, ITimerFactory timerFactory) { _securityManager = securityManager; + _timerFactory = timerFactory; _logger = logManager.GetLogger("Registration Loader"); } @@ -41,7 +43,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints /// </summary> public void Run() { - _timer = new PeriodicTimer(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12)); + _timer = _timerFactory.Create(s => LoadAllRegistrations(), null, TimeSpan.FromMilliseconds(100), TimeSpan.FromHours(12)); } private async Task LoadAllRegistrations() diff --git a/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs index 414fda400..b674fc39b 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/RecordingNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/RecordingNotifier.cs @@ -7,7 +7,7 @@ using MediaBrowser.Controller.Plugins; using MediaBrowser.Controller.Session; using MediaBrowser.Model.Logging; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { public class RecordingNotifier : IServerEntryPoint { @@ -32,22 +32,22 @@ namespace MediaBrowser.Server.Implementations.EntryPoints _liveTvManager.SeriesTimerCreated += _liveTvManager_SeriesTimerCreated; } - private void _liveTvManager_SeriesTimerCreated(object sender, Model.Events.GenericEventArgs<TimerEventInfo> e) + private void _liveTvManager_SeriesTimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) { SendMessage("SeriesTimerCreated", e.Argument); } - private void _liveTvManager_TimerCreated(object sender, Model.Events.GenericEventArgs<TimerEventInfo> e) + private void _liveTvManager_TimerCreated(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) { SendMessage("TimerCreated", e.Argument); } - private void _liveTvManager_SeriesTimerCancelled(object sender, Model.Events.GenericEventArgs<TimerEventInfo> e) + private void _liveTvManager_SeriesTimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) { SendMessage("SeriesTimerCancelled", e.Argument); } - private void _liveTvManager_TimerCancelled(object sender, Model.Events.GenericEventArgs<TimerEventInfo> e) + private void _liveTvManager_TimerCancelled(object sender, MediaBrowser.Model.Events.GenericEventArgs<TimerEventInfo> e) { SendMessage("TimerCancelled", e.Argument); } diff --git a/MediaBrowser.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs index a0b7ff515..77de849a1 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs +++ b/Emby.Server.Implementations/EntryPoints/RefreshUsersMetadata.cs @@ -2,7 +2,7 @@ using MediaBrowser.Controller.Plugins; using System.Threading; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { /// <summary> /// Class RefreshUsersMetadata diff --git a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs index 0da48a2d8..4d640bc95 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/ServerEventNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/ServerEventNotifier.cs @@ -14,7 +14,7 @@ using System.Collections.Generic; using System.Threading; using MediaBrowser.Model.Tasks; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { /// <summary> /// Class WebSocketEvents diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs index d14bd4368..1b897ca29 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/UsageEntryPoint.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageEntryPoint.cs @@ -12,7 +12,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Controller.Configuration; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { /// <summary> /// Class UsageEntryPoint diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs index e445300e4..be848acb7 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/UsageReporter.cs +++ b/Emby.Server.Implementations/EntryPoints/UsageReporter.cs @@ -10,7 +10,7 @@ using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Logging; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { public class UsageReporter { diff --git a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs index 8262048b1..b93410180 100644 --- a/MediaBrowser.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs +++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs @@ -11,8 +11,9 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Extensions; +using MediaBrowser.Model.Threading; -namespace MediaBrowser.Server.Implementations.EntryPoints +namespace Emby.Server.Implementations.EntryPoints { class UserDataChangeNotifier : IServerEntryPoint { @@ -22,17 +23,19 @@ namespace MediaBrowser.Server.Implementations.EntryPoints private readonly IUserManager _userManager; private readonly object _syncLock = new object(); - private Timer UpdateTimer { get; set; } + private ITimer UpdateTimer { get; set; } + private readonly ITimerFactory _timerFactory; private const int UpdateDuration = 500; private readonly Dictionary<Guid, List<IHasUserData>> _changedItems = new Dictionary<Guid, List<IHasUserData>>(); - public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager) + public UserDataChangeNotifier(IUserDataManager userDataManager, ISessionManager sessionManager, ILogger logger, IUserManager userManager, ITimerFactory timerFactory) { _userDataManager = userDataManager; _sessionManager = sessionManager; _logger = logger; _userManager = userManager; + _timerFactory = timerFactory; } public void Run() @@ -51,7 +54,7 @@ namespace MediaBrowser.Server.Implementations.EntryPoints { if (UpdateTimer == null) { - UpdateTimer = new Timer(UpdateTimerCallback, null, UpdateDuration, + UpdateTimer = _timerFactory.Create(UpdateTimerCallback, null, UpdateDuration, Timeout.Infinite); } else diff --git a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs index 9f1744cc9..204e04061 100644 --- a/MediaBrowser.Server.Implementations/MediaEncoder/EncodingManager.cs +++ b/Emby.Server.Implementations/MediaEncoder/EncodingManager.cs @@ -18,7 +18,7 @@ using MediaBrowser.Controller.IO; using MediaBrowser.Model.IO; using MediaBrowser.Controller.Library; -namespace MediaBrowser.Server.Implementations.MediaEncoder +namespace Emby.Server.Implementations.MediaEncoder { public class EncodingManager : IEncodingManager { @@ -143,11 +143,11 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder var container = video.Container; var tempFile = await _encoder.ExtractVideoImage(inputPath, container, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false); - File.Copy(tempFile, path, true); + _fileSystem.CopyFile(tempFile, path, true); try { - File.Delete(tempFile); + _fileSystem.DeleteFile(tempFile); } catch { @@ -205,7 +205,7 @@ namespace MediaBrowser.Server.Implementations.MediaEncoder return _fileSystem.GetFilePaths(path) .ToList(); } - catch (DirectoryNotFoundException) + catch (IOException) { return new List<string>(); } diff --git a/Emby.Server.Implementations/News/NewsEntryPoint.cs b/Emby.Server.Implementations/News/NewsEntryPoint.cs new file mode 100644 index 000000000..1497d066f --- /dev/null +++ b/Emby.Server.Implementations/News/NewsEntryPoint.cs @@ -0,0 +1,260 @@ +using MediaBrowser.Common.Configuration; +using MediaBrowser.Common.Extensions; +using MediaBrowser.Common.Net; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Notifications; +using MediaBrowser.Controller.Plugins; +using MediaBrowser.Model.Logging; +using MediaBrowser.Model.News; +using MediaBrowser.Model.Notifications; +using MediaBrowser.Model.Serialization; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using MediaBrowser.Model.IO; +using MediaBrowser.Model.Threading; + +namespace Emby.Server.Implementations.News +{ + public class NewsEntryPoint : IServerEntryPoint + { + private ITimer _timer; + private readonly IHttpClient _httpClient; + private readonly IApplicationPaths _appPaths; + private readonly IFileSystem _fileSystem; + private readonly ILogger _logger; + private readonly IJsonSerializer _json; + + private readonly INotificationManager _notifications; + private readonly IUserManager _userManager; + + private readonly TimeSpan _frequency = TimeSpan.FromHours(24); + private readonly ITimerFactory _timerFactory; + + public NewsEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IJsonSerializer json, INotificationManager notifications, IUserManager userManager, ITimerFactory timerFactory) + { + _httpClient = httpClient; + _appPaths = appPaths; + _fileSystem = fileSystem; + _logger = logger; + _json = json; + _notifications = notifications; + _userManager = userManager; + _timerFactory = timerFactory; + } + + public void Run() + { + _timer = _timerFactory.Create(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency); + } + + /// <summary> + /// Called when [timer fired]. + /// </summary> + /// <param name="state">The state.</param> + private async void OnTimerFired(object state) + { + var path = Path.Combine(_appPaths.CachePath, "news.json"); + + try + { + await DownloadNews(path).ConfigureAwait(false); + } + catch (Exception ex) + { + _logger.ErrorException("Error downloading news", ex); + } + } + + private async Task DownloadNews(string path) + { + DateTime? lastUpdate = null; + + if (_fileSystem.FileExists(path)) + { + lastUpdate = _fileSystem.GetLastWriteTimeUtc(path); + } + + var requestOptions = new HttpRequestOptions + { + Url = "http://emby.media/community/index.php?/blog/rss/1-media-browser-developers-blog", + Progress = new Progress<double>(), + UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.42 Safari/537.36", + BufferContent = false + }; + + using (var stream = await _httpClient.Get(requestOptions).ConfigureAwait(false)) + { + using (var reader = XmlReader.Create(stream)) + { + var news = ParseRssItems(reader).ToList(); + + _json.SerializeToFile(news, path); + + await CreateNotifications(news, lastUpdate, CancellationToken.None).ConfigureAwait(false); + } + } + } + + private Task CreateNotifications(List<NewsItem> items, DateTime? lastUpdate, CancellationToken cancellationToken) + { + if (lastUpdate.HasValue) + { + items = items.Where(i => i.Date.ToUniversalTime() >= lastUpdate.Value) + .ToList(); + } + + var tasks = items.Select(i => _notifications.SendNotification(new NotificationRequest + { + Date = i.Date, + Name = i.Title, + Description = i.Description, + Url = i.Link, + UserIds = _userManager.Users.Select(u => u.Id.ToString("N")).ToList() + + }, cancellationToken)); + + return Task.WhenAll(tasks); + } + + private IEnumerable<NewsItem> ParseRssItems(XmlReader reader) + { + reader.MoveToContent(); + reader.Read(); + + while (!reader.EOF) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "channel": + { + using (var subReader = reader.ReadSubtree()) + { + return ParseFromChannelNode(subReader); + } + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return new List<NewsItem>(); + } + + private IEnumerable<NewsItem> ParseFromChannelNode(XmlReader reader) + { + var list = new List<NewsItem>(); + + reader.MoveToContent(); + reader.Read(); + + while (!reader.EOF) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "item": + { + using (var subReader = reader.ReadSubtree()) + { + list.Add(ParseItem(subReader)); + } + break; + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return list; + } + + private NewsItem ParseItem(XmlReader reader) + { + var item = new NewsItem(); + + reader.MoveToContent(); + reader.Read(); + + while (!reader.EOF) + { + if (reader.NodeType == XmlNodeType.Element) + { + switch (reader.Name) + { + case "title": + { + item.Title = reader.ReadElementContentAsString(); + break; + } + case "link": + { + item.Link = reader.ReadElementContentAsString(); + break; + } + case "description": + { + item.DescriptionHtml = reader.ReadElementContentAsString(); + item.Description = item.DescriptionHtml.StripHtml(); + break; + } + case "pubDate": + { + var date = reader.ReadElementContentAsString(); + DateTime parsedDate; + + if (DateTime.TryParse(date, out parsedDate)) + { + item.Date = parsedDate; + } + break; + } + default: + { + reader.Skip(); + break; + } + } + } + else + { + reader.Read(); + } + } + + return item; + } + + public void Dispose() + { + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + } +} diff --git a/Emby.Server.Implementations/project.json b/Emby.Server.Implementations/project.json index b1373cd45..81f816753 100644 --- a/Emby.Server.Implementations/project.json +++ b/Emby.Server.Implementations/project.json @@ -1,8 +1,7 @@ -{ +{ "supports": {}, "dependencies": { - "MediaBrowser.Naming": "1.0.0.59", - "Patterns.Logging": "1.0.0.6" + "MediaBrowser.Naming": "1.0.0.59" }, "frameworks": { ".NETPortable,Version=v4.5,Profile=Profile7": {} diff --git a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj index 88e9478df..b31c8286c 100644 --- a/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj +++ b/MediaBrowser.Server.Implementations/MediaBrowser.Server.Implementations.csproj @@ -46,17 +46,13 @@ <HintPath>..\ThirdParty\emby\Emby.Common.Implementations.dll</HintPath> </Reference> <Reference Include="Emby.XmlTv, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\Emby.XmlTv.1.0.0.62\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath> + <HintPath>..\packages\Emby.XmlTv.1.0.0.63\lib\portable-net45+win8\Emby.XmlTv.dll</HintPath> <Private>True</Private> </Reference> <Reference Include="INIFileParser, Version=2.3.0.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL"> <HintPath>..\packages\ini-parser.2.3.0\lib\net20\INIFileParser.dll</HintPath> <Private>True</Private> </Reference> - <Reference Include="MediaBrowser.Naming, Version=1.0.6151.30291, Culture=neutral, processorArchitecture=MSIL"> - <HintPath>..\packages\MediaBrowser.Naming.1.0.0.59\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath> - <Private>True</Private> - </Reference> <Reference Include="Microsoft.IO.RecyclableMemoryStream, Version=1.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"> <HintPath>..\packages\Microsoft.IO.RecyclableMemoryStream.1.1.0.0\lib\net45\Microsoft.IO.RecyclableMemoryStream.dll</HintPath> <Private>True</Private> @@ -120,17 +116,8 @@ <Compile Include="Connect\Validator.cs" /> <Compile Include="Devices\DeviceRepository.cs" /> <Compile Include="Devices\CameraUploadsFolder.cs" /> - <Compile Include="EntryPoints\AutomaticRestartEntryPoint.cs" /> <Compile Include="EntryPoints\ExternalPortForwarding.cs" /> - <Compile Include="EntryPoints\LibraryChangedNotifier.cs" /> - <Compile Include="EntryPoints\LoadRegistrations.cs" /> - <Compile Include="EntryPoints\RecordingNotifier.cs" /> - <Compile Include="EntryPoints\RefreshUsersMetadata.cs" /> - <Compile Include="EntryPoints\UsageEntryPoint.cs" /> - <Compile Include="EntryPoints\UsageReporter.cs" /> <Compile Include="EntryPoints\UdpServerEntryPoint.cs" /> - <Compile Include="EntryPoints\ServerEventNotifier.cs" /> - <Compile Include="EntryPoints\UserDataChangeNotifier.cs" /> <Compile Include="HttpServer\IHttpListener.cs" /> <Compile Include="HttpServer\Security\AuthorizationContext.cs" /> <Compile Include="HttpServer\ContainerAdapter.cs" /> @@ -211,8 +198,6 @@ <Compile Include="LiveTv\TunerHosts\SatIp\Utils.cs" /> <Compile Include="Localization\LocalizationManager.cs" /> <Compile Include="Logging\PatternsLogger.cs" /> - <Compile Include="MediaEncoder\EncodingManager.cs" /> - <Compile Include="News\NewsEntryPoint.cs" /> <Compile Include="Persistence\BaseSqliteRepository.cs" /> <Compile Include="Persistence\DataExtensions.cs" /> <Compile Include="Persistence\IDbConnector.cs" /> @@ -287,23 +272,6 @@ </ProjectReference> </ItemGroup> <ItemGroup> - <EmbeddedResource Include="Localization\Ratings\us.txt" /> - <EmbeddedResource Include="Localization\Ratings\au.txt" /> - <EmbeddedResource Include="Localization\Ratings\gb.txt" /> - <EmbeddedResource Include="Localization\Ratings\nl.txt" /> - <EmbeddedResource Include="Localization\Ratings\br.txt" /> - <EmbeddedResource Include="Localization\Ratings\dk.txt" /> - <EmbeddedResource Include="Localization\Ratings\de.txt" /> - <EmbeddedResource Include="Localization\Ratings\mx.txt" /> - <EmbeddedResource Include="Localization\Ratings\co.txt" /> - <EmbeddedResource Include="Localization\Ratings\fr.txt" /> - <EmbeddedResource Include="Localization\Ratings\ie.txt" /> - <EmbeddedResource Include="Localization\Ratings\jp.txt" /> - <EmbeddedResource Include="Localization\Ratings\kz.txt" /> - <EmbeddedResource Include="Localization\Ratings\nz.txt" /> - <EmbeddedResource Include="Localization\Ratings\ru.txt" /> - </ItemGroup> - <ItemGroup> <Content Include="..\ThirdParty\ServiceStack\swagger-ui\lib\backbone-min.js"> <Link>swagger-ui\lib\backbone-min.js</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -368,7 +336,6 @@ <Link>swagger-ui\swagger-ui.min.js</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - <EmbeddedResource Include="Localization\countries.json" /> <Content Include="..\ThirdParty\ServiceStack\swagger-ui\fonts\droid-sans-v6-latin-700.eot"> <Link>swagger-ui\fonts\droid-sans-v6-latin-700.eot</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -401,46 +368,25 @@ <Link>swagger-ui\fonts\droid-sans-v6-latin-regular.woff2</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> + <EmbeddedResource Include="Localization\Ratings\us.txt" /> + <EmbeddedResource Include="Localization\Ratings\ru.txt" /> + <EmbeddedResource Include="Localization\Ratings\nz.txt" /> + <EmbeddedResource Include="Localization\Ratings\nl.txt" /> + <EmbeddedResource Include="Localization\Ratings\mx.txt" /> + <EmbeddedResource Include="Localization\Ratings\kz.txt" /> + <EmbeddedResource Include="Localization\Ratings\jp.txt" /> + <EmbeddedResource Include="Localization\Ratings\ie.txt" /> + <EmbeddedResource Include="Localization\Ratings\gb.txt" /> + <EmbeddedResource Include="Localization\Ratings\fr.txt" /> + <EmbeddedResource Include="Localization\Ratings\dk.txt" /> + <EmbeddedResource Include="Localization\Ratings\de.txt" /> + <EmbeddedResource Include="Localization\Ratings\co.txt" /> + <EmbeddedResource Include="Localization\Ratings\ca.txt" /> + <EmbeddedResource Include="Localization\Ratings\br.txt" /> + <EmbeddedResource Include="Localization\Ratings\be.txt" /> + <EmbeddedResource Include="Localization\Ratings\au.txt" /> + <EmbeddedResource Include="Localization\iso6392.txt" /> <None Include="app.config" /> - <EmbeddedResource Include="Localization\Core\core.json" /> - <EmbeddedResource Include="Localization\Core\ar.json" /> - <EmbeddedResource Include="Localization\Core\bg-BG.json" /> - <EmbeddedResource Include="Localization\Core\ca.json" /> - <EmbeddedResource Include="Localization\Core\cs.json" /> - <EmbeddedResource Include="Localization\Core\da.json" /> - <EmbeddedResource Include="Localization\Core\de.json" /> - <EmbeddedResource Include="Localization\Core\el.json" /> - <EmbeddedResource Include="Localization\Core\en-GB.json" /> - <EmbeddedResource Include="Localization\Core\en-US.json" /> - <EmbeddedResource Include="Localization\Core\es-AR.json" /> - <EmbeddedResource Include="Localization\Core\es-MX.json" /> - <EmbeddedResource Include="Localization\Core\es.json" /> - <EmbeddedResource Include="Localization\Core\fi.json" /> - <EmbeddedResource Include="Localization\Core\fr.json" /> - <EmbeddedResource Include="Localization\Core\gsw.json" /> - <EmbeddedResource Include="Localization\Core\he.json" /> - <EmbeddedResource Include="Localization\Core\hr.json" /> - <EmbeddedResource Include="Localization\Core\it.json" /> - <EmbeddedResource Include="Localization\Core\kk.json" /> - <EmbeddedResource Include="Localization\Core\ko.json" /> - <EmbeddedResource Include="Localization\Core\ms.json" /> - <EmbeddedResource Include="Localization\Core\nb.json" /> - <EmbeddedResource Include="Localization\Core\nl.json" /> - <EmbeddedResource Include="Localization\Core\pl.json" /> - <EmbeddedResource Include="Localization\Core\pt-BR.json" /> - <EmbeddedResource Include="Localization\Core\pt-PT.json" /> - <EmbeddedResource Include="Localization\Core\ro.json" /> - <EmbeddedResource Include="Localization\Core\ru.json" /> - <EmbeddedResource Include="Localization\Core\sl-SI.json" /> - <EmbeddedResource Include="Localization\Core\sv.json" /> - <EmbeddedResource Include="Localization\Core\tr.json" /> - <EmbeddedResource Include="Localization\Core\uk.json" /> - <EmbeddedResource Include="Localization\Core\vi.json" /> - <EmbeddedResource Include="Localization\Core\zh-CN.json" /> - <EmbeddedResource Include="Localization\Core\zh-TW.json" /> - <EmbeddedResource Include="Localization\Core\zh-HK.json" /> - <EmbeddedResource Include="Localization\Core\hu.json" /> - <EmbeddedResource Include="Localization\Core\id.json" /> <EmbeddedResource Include="LiveTv\TunerHosts\SatIp\ini\satellite\0030.ini" /> <EmbeddedResource Include="LiveTv\TunerHosts\SatIp\ini\satellite\0049.ini" /> <EmbeddedResource Include="LiveTv\TunerHosts\SatIp\ini\satellite\0070.ini" /> @@ -610,13 +556,50 @@ <EmbeddedResource Include="LiveTv\TunerHosts\SatIp\ini\satellite\3560.ini" /> <EmbeddedResource Include="LiveTv\TunerHosts\SatIp\ini\satellite\3592.ini" /> <EmbeddedResource Include="LiveTv\TunerHosts\SatIp\ini\satellite\3594.ini" /> + <EmbeddedResource Include="Localization\Core\ar.json" /> + <EmbeddedResource Include="Localization\Core\bg-BG.json" /> + <EmbeddedResource Include="Localization\Core\ca.json" /> + <EmbeddedResource Include="Localization\Core\core.json" /> + <EmbeddedResource Include="Localization\Core\cs.json" /> + <EmbeddedResource Include="Localization\Core\da.json" /> + <EmbeddedResource Include="Localization\Core\de.json" /> + <EmbeddedResource Include="Localization\Core\el.json" /> + <EmbeddedResource Include="Localization\Core\en-GB.json" /> + <EmbeddedResource Include="Localization\Core\en-US.json" /> + <EmbeddedResource Include="Localization\Core\es-AR.json" /> + <EmbeddedResource Include="Localization\Core\es-MX.json" /> + <EmbeddedResource Include="Localization\Core\es.json" /> + <EmbeddedResource Include="Localization\Core\fi.json" /> <EmbeddedResource Include="Localization\Core\fr-CA.json" /> + <EmbeddedResource Include="Localization\Core\fr.json" /> + <EmbeddedResource Include="Localization\Core\gsw.json" /> + <EmbeddedResource Include="Localization\Core\he.json" /> + <EmbeddedResource Include="Localization\Core\hr.json" /> + <EmbeddedResource Include="Localization\Core\hu.json" /> + <EmbeddedResource Include="Localization\Core\id.json" /> + <EmbeddedResource Include="Localization\Core\it.json" /> + <EmbeddedResource Include="Localization\Core\kk.json" /> + <EmbeddedResource Include="Localization\Core\ko.json" /> + <EmbeddedResource Include="Localization\Core\ms.json" /> + <EmbeddedResource Include="Localization\Core\nb.json" /> + <EmbeddedResource Include="Localization\Core\nl.json" /> + <EmbeddedResource Include="Localization\Core\pl.json" /> + <EmbeddedResource Include="Localization\Core\pt-BR.json" /> + <EmbeddedResource Include="Localization\Core\pt-PT.json" /> + <EmbeddedResource Include="Localization\Core\ro.json" /> + <EmbeddedResource Include="Localization\Core\ru.json" /> + <EmbeddedResource Include="Localization\Core\sl-SI.json" /> + <EmbeddedResource Include="Localization\Core\sv.json" /> + <EmbeddedResource Include="Localization\Core\tr.json" /> + <EmbeddedResource Include="Localization\Core\uk.json" /> + <EmbeddedResource Include="Localization\Core\vi.json" /> + <EmbeddedResource Include="Localization\Core\zh-CN.json" /> + <EmbeddedResource Include="Localization\Core\zh-HK.json" /> + <EmbeddedResource Include="Localization\Core\zh-TW.json" /> + <EmbeddedResource Include="Localization\countries.json" /> <None Include="packages.config" /> </ItemGroup> <ItemGroup> - <EmbeddedResource Include="Localization\Ratings\ca.txt" /> - </ItemGroup> - <ItemGroup> <Content Include="..\ThirdParty\ServiceStack\swagger-ui\css\reset.css"> <Link>swagger-ui\css\reset.css</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> @@ -665,8 +648,6 @@ <Link>swagger-ui\lib\shred\content.js</Link> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </Content> - <EmbeddedResource Include="Localization\iso6392.txt" /> - <EmbeddedResource Include="Localization\Ratings\be.txt" /> </ItemGroup> <ItemGroup /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> diff --git a/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs b/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs deleted file mode 100644 index 22cb4e86a..000000000 --- a/MediaBrowser.Server.Implementations/News/NewsEntryPoint.cs +++ /dev/null @@ -1,170 +0,0 @@ -using MediaBrowser.Common.Configuration; -using MediaBrowser.Common.Extensions; -using MediaBrowser.Common.Net; -using MediaBrowser.Controller.Library; -using MediaBrowser.Controller.Notifications; -using MediaBrowser.Controller.Plugins; -using MediaBrowser.Model.Logging; -using MediaBrowser.Model.News; -using MediaBrowser.Model.Notifications; -using MediaBrowser.Model.Serialization; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using System.Xml; -using MediaBrowser.Common.IO; -using MediaBrowser.Controller.IO; -using MediaBrowser.Model.IO; -using MediaBrowser.Server.Implementations.Threading; - -namespace MediaBrowser.Server.Implementations.News -{ - public class NewsEntryPoint : IServerEntryPoint - { - private PeriodicTimer _timer; - private readonly IHttpClient _httpClient; - private readonly IApplicationPaths _appPaths; - private readonly IFileSystem _fileSystem; - private readonly ILogger _logger; - private readonly IJsonSerializer _json; - - private readonly INotificationManager _notifications; - private readonly IUserManager _userManager; - - private readonly TimeSpan _frequency = TimeSpan.FromHours(24); - - public NewsEntryPoint(IHttpClient httpClient, IApplicationPaths appPaths, IFileSystem fileSystem, ILogger logger, IJsonSerializer json, INotificationManager notifications, IUserManager userManager) - { - _httpClient = httpClient; - _appPaths = appPaths; - _fileSystem = fileSystem; - _logger = logger; - _json = json; - _notifications = notifications; - _userManager = userManager; - } - - public void Run() - { - _timer = new PeriodicTimer(OnTimerFired, null, TimeSpan.FromMilliseconds(500), _frequency); - } - - /// <summary> - /// Called when [timer fired]. - /// </summary> - /// <param name="state">The state.</param> - private async void OnTimerFired(object state) - { - var path = Path.Combine(_appPaths.CachePath, "news.json"); - - try - { - await DownloadNews(path).ConfigureAwait(false); - } - catch (Exception ex) - { - _logger.ErrorException("Error downloading news", ex); - } - } - - private async Task DownloadNews(string path) - { - DateTime? lastUpdate = null; - - if (_fileSystem.FileExists(path)) - { - lastUpdate = _fileSystem.GetLastWriteTimeUtc(path); - } - - var requestOptions = new HttpRequestOptions - { - Url = "http://emby.media/community/index.php?/blog/rss/1-media-browser-developers-blog", - Progress = new Progress<double>(), - UserAgent = "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.42 Safari/537.36", - BufferContent = false - }; - - using (var stream = await _httpClient.Get(requestOptions).ConfigureAwait(false)) - { - var doc = new XmlDocument(); - doc.Load(stream); - - var news = ParseRssItems(doc).ToList(); - - _json.SerializeToFile(news, path); - - await CreateNotifications(news, lastUpdate, CancellationToken.None).ConfigureAwait(false); - } - } - - private Task CreateNotifications(List<NewsItem> items, DateTime? lastUpdate, CancellationToken cancellationToken) - { - if (lastUpdate.HasValue) - { - items = items.Where(i => i.Date.ToUniversalTime() >= lastUpdate.Value) - .ToList(); - } - - var tasks = items.Select(i => _notifications.SendNotification(new NotificationRequest - { - Date = i.Date, - Name = i.Title, - Description = i.Description, - Url = i.Link, - UserIds = _userManager.Users.Select(u => u.Id.ToString("N")).ToList() - - }, cancellationToken)); - - return Task.WhenAll(tasks); - } - - private IEnumerable<NewsItem> ParseRssItems(XmlDocument xmlDoc) - { - var nodes = xmlDoc.SelectNodes("rss/channel/item"); - - if (nodes != null) - { - foreach (XmlNode node in nodes) - { - var newsItem = new NewsItem(); - - newsItem.Title = ParseDocElements(node, "title"); - - newsItem.DescriptionHtml = ParseDocElements(node, "description"); - newsItem.Description = newsItem.DescriptionHtml.StripHtml(); - - newsItem.Link = ParseDocElements(node, "link"); - - var date = ParseDocElements(node, "pubDate"); - DateTime parsedDate; - - if (DateTime.TryParse(date, out parsedDate)) - { - newsItem.Date = parsedDate; - } - - yield return newsItem; - } - } - } - - private string ParseDocElements(XmlNode parent, string xPath) - { - var node = parent.SelectSingleNode(xPath); - - return node != null ? node.InnerText : string.Empty; - } - - public void Dispose() - { - if (_timer != null) - { - _timer.Dispose(); - _timer = null; - } - } - } -} diff --git a/MediaBrowser.Server.Implementations/packages.config b/MediaBrowser.Server.Implementations/packages.config index cdfb48abe..5a43abe0f 100644 --- a/MediaBrowser.Server.Implementations/packages.config +++ b/MediaBrowser.Server.Implementations/packages.config @@ -1,8 +1,7 @@ <?xml version="1.0" encoding="utf-8"?>
<packages>
- <package id="Emby.XmlTv" version="1.0.0.62" targetFramework="net46" />
+ <package id="Emby.XmlTv" version="1.0.0.63" targetFramework="net46" />
<package id="ini-parser" version="2.3.0" targetFramework="net45" />
- <package id="MediaBrowser.Naming" version="1.0.0.59" targetFramework="net46" />
<package id="Microsoft.IO.RecyclableMemoryStream" version="1.1.0.0" targetFramework="net46" />
<package id="Patterns.Logging" version="1.0.0.6" targetFramework="net46" />
<package id="SocketHttpListener" version="1.0.0.40" targetFramework="net45" />
diff --git a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs index 294e780cc..861d59908 100644 --- a/MediaBrowser.Server.Startup.Common/ApplicationHost.cs +++ b/MediaBrowser.Server.Startup.Common/ApplicationHost.cs @@ -59,7 +59,6 @@ using MediaBrowser.Server.Implementations.HttpServer.Security; using MediaBrowser.Server.Implementations.IO; using MediaBrowser.Server.Implementations.LiveTv; using MediaBrowser.Server.Implementations.Localization; -using MediaBrowser.Server.Implementations.MediaEncoder; using MediaBrowser.Server.Implementations.Notifications; using MediaBrowser.Server.Implementations.Persistence; using MediaBrowser.Server.Implementations.Security; @@ -107,6 +106,7 @@ using Emby.Server.Implementations.Devices; using Emby.Server.Implementations.Dto; using Emby.Server.Implementations.FileOrganization; using Emby.Server.Implementations.Library; +using Emby.Server.Implementations.MediaEncoder; using Emby.Server.Implementations.Notifications; using Emby.Server.Implementations.Persistence; using Emby.Server.Implementations.Playlists; diff --git a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj index c64f065d7..6a84ee0c5 100644 --- a/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj +++ b/MediaBrowser.Server.Startup.Common/MediaBrowser.Server.Startup.Common.csproj @@ -38,6 +38,10 @@ <Reference Include="Emby.Dlna"> <HintPath>..\ThirdParty\emby\Emby.Dlna.dll</HintPath> </Reference> + <Reference Include="MediaBrowser.Naming, Version=1.0.6151.30291, Culture=neutral, processorArchitecture=MSIL"> + <HintPath>..\packages\MediaBrowser.Naming.1.0.0.59\lib\portable-net45+win8\MediaBrowser.Naming.dll</HintPath> + <Private>True</Private> + </Reference> <Reference Include="Mono.Posix, Version=4.0.0.0, Culture=neutral, PublicKeyToken=0738eb9f132ed756, processorArchitecture=MSIL"> <SpecificVersion>False</SpecificVersion> <HintPath>..\packages\Mono.Posix.4.0.0.0\lib\net40\Mono.Posix.dll</HintPath> diff --git a/MediaBrowser.Server.Startup.Common/packages.config b/MediaBrowser.Server.Startup.Common/packages.config index aa11730c7..ea3d0e7f8 100644 --- a/MediaBrowser.Server.Startup.Common/packages.config +++ b/MediaBrowser.Server.Startup.Common/packages.config @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <packages> + <package id="MediaBrowser.Naming" version="1.0.0.59" targetFramework="net46" /> <package id="Mono.Posix" version="4.0.0.0" targetFramework="net45" /> <package id="Patterns.Logging" version="1.0.0.6" targetFramework="net46" /> <package id="SimpleInjector" version="3.2.4" targetFramework="net46" /> diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj index 0a86e96a5..e84fa8e65 100644 --- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj +++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj @@ -33,11 +33,6 @@ <WarningLevel>4</WarningLevel> </PropertyGroup> <ItemGroup> - <Reference Include="Patterns.Logging"> - <HintPath>..\packages\Patterns.Logging.1.0.0.2\lib\portable-net45+sl4+wp71+win8+wpa81\Patterns.Logging.dll</HintPath> - </Reference> - </ItemGroup> - <ItemGroup> <Compile Include="..\SharedVersion.cs"> <Link>Properties\SharedVersion.cs</Link> </Compile> diff --git a/MediaBrowser.XbmcMetadata/packages.config b/MediaBrowser.XbmcMetadata/packages.config index 79ff26ec6..6b8deb9c9 100644 --- a/MediaBrowser.XbmcMetadata/packages.config +++ b/MediaBrowser.XbmcMetadata/packages.config @@ -1,4 +1,3 @@ <?xml version="1.0" encoding="utf-8"?> <packages> - <package id="Patterns.Logging" version="1.0.0.2" targetFramework="net45" /> </packages>
\ No newline at end of file |
