Merge pull request #7840 from adrez99/gzip
This commit is contained in:
commit
a274f4a688
|
@ -22,7 +22,6 @@ using Emby.Drawing;
|
||||||
using Emby.Naming.Common;
|
using Emby.Naming.Common;
|
||||||
using Emby.Notifications;
|
using Emby.Notifications;
|
||||||
using Emby.Photos;
|
using Emby.Photos;
|
||||||
using Emby.Server.Implementations.Archiving;
|
|
||||||
using Emby.Server.Implementations.Channels;
|
using Emby.Server.Implementations.Channels;
|
||||||
using Emby.Server.Implementations.Collections;
|
using Emby.Server.Implementations.Collections;
|
||||||
using Emby.Server.Implementations.Configuration;
|
using Emby.Server.Implementations.Configuration;
|
||||||
|
@ -561,8 +560,6 @@ namespace Emby.Server.Implementations
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
serviceCollection.AddSingleton<IInstallationManager, InstallationManager>();
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IZipClient, ZipClient>();
|
|
||||||
|
|
||||||
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
serviceCollection.AddSingleton<IServerApplicationHost>(this);
|
||||||
serviceCollection.AddSingleton(ApplicationPaths);
|
serviceCollection.AddSingleton(ApplicationPaths);
|
||||||
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
using System.IO;
|
|
||||||
using MediaBrowser.Model.IO;
|
|
||||||
using SharpCompress.Common;
|
|
||||||
using SharpCompress.Readers;
|
|
||||||
using SharpCompress.Readers.GZip;
|
|
||||||
|
|
||||||
namespace Emby.Server.Implementations.Archiving
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Class DotNetZipClient.
|
|
||||||
/// </summary>
|
|
||||||
public class ZipClient : IZipClient
|
|
||||||
{
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles)
|
|
||||||
{
|
|
||||||
using var reader = GZipReader.Open(source);
|
|
||||||
var options = new ExtractionOptions
|
|
||||||
{
|
|
||||||
ExtractFullPath = true,
|
|
||||||
Overwrite = overwriteExistingFiles
|
|
||||||
};
|
|
||||||
|
|
||||||
Directory.CreateDirectory(targetPath);
|
|
||||||
reader.WriteAllToDirectory(targetPath, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName)
|
|
||||||
{
|
|
||||||
using var reader = GZipReader.Open(source);
|
|
||||||
if (reader.MoveToNextEntry())
|
|
||||||
{
|
|
||||||
var entry = reader.Entry;
|
|
||||||
|
|
||||||
var filename = entry.Key;
|
|
||||||
if (string.IsNullOrWhiteSpace(filename))
|
|
||||||
{
|
|
||||||
filename = defaultFileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.WriteEntryToFile(Path.Combine(targetPath, filename));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -32,7 +32,6 @@
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="6.0.9" />
|
||||||
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
<PackageReference Include="Mono.Nat" Version="3.0.3" />
|
||||||
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
|
<PackageReference Include="prometheus-net.DotNetRuntime" Version="4.2.4" />
|
||||||
<PackageReference Include="sharpcompress" Version="0.32.2" />
|
|
||||||
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
<PackageReference Include="SQLitePCL.pretty.netstandard" Version="3.1.0" />
|
||||||
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -6,9 +6,9 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.IO.Compression;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Security.Cryptography;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Extensions;
|
using Jellyfin.Extensions;
|
||||||
|
@ -33,20 +33,17 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
private readonly IHttpClientFactory _httpClientFactory;
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
private readonly ILogger<XmlTvListingsProvider> _logger;
|
private readonly ILogger<XmlTvListingsProvider> _logger;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IZipClient _zipClient;
|
|
||||||
|
|
||||||
public XmlTvListingsProvider(
|
public XmlTvListingsProvider(
|
||||||
IServerConfigurationManager config,
|
IServerConfigurationManager config,
|
||||||
IHttpClientFactory httpClientFactory,
|
IHttpClientFactory httpClientFactory,
|
||||||
ILogger<XmlTvListingsProvider> logger,
|
ILogger<XmlTvListingsProvider> logger,
|
||||||
IFileSystem fileSystem,
|
IFileSystem fileSystem)
|
||||||
IZipClient zipClient)
|
|
||||||
{
|
{
|
||||||
_config = config;
|
_config = config;
|
||||||
_httpClientFactory = httpClientFactory;
|
_httpClientFactory = httpClientFactory;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_fileSystem = fileSystem;
|
_fileSystem = fileSystem;
|
||||||
_zipClient = zipClient;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Name => "XmlTV";
|
public string Name => "XmlTV";
|
||||||
|
@ -67,16 +64,12 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
{
|
{
|
||||||
_logger.LogInformation("xmltv path: {Path}", info.Path);
|
_logger.LogInformation("xmltv path: {Path}", info.Path);
|
||||||
|
|
||||||
if (!info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return UnzipIfNeeded(info.Path, info.Path);
|
|
||||||
}
|
|
||||||
|
|
||||||
string cacheFilename = info.Id + ".xml";
|
string cacheFilename = info.Id + ".xml";
|
||||||
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
|
string cacheFile = Path.Combine(_config.ApplicationPaths.CachePath, "xmltv", cacheFilename);
|
||||||
|
|
||||||
if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
|
if (File.Exists(cacheFile) && File.GetLastWriteTimeUtc(cacheFile) >= DateTime.UtcNow.Subtract(_maxCacheAge))
|
||||||
{
|
{
|
||||||
return UnzipIfNeeded(info.Path, cacheFile);
|
return cacheFile;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must check if file exists as parent directory may not exist.
|
// Must check if file exists as parent directory may not exist.
|
||||||
|
@ -84,95 +77,55 @@ namespace Emby.Server.Implementations.LiveTv.Listings
|
||||||
{
|
{
|
||||||
File.Delete(cacheFile);
|
File.Delete(cacheFile);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
|
|
||||||
|
|
||||||
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
|
||||||
|
|
||||||
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false);
|
|
||||||
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
|
||||||
await using (var fileStream = new FileStream(cacheFile, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, FileOptions.Asynchronous))
|
|
||||||
{
|
{
|
||||||
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
Directory.CreateDirectory(Path.GetDirectoryName(cacheFile));
|
||||||
}
|
}
|
||||||
|
|
||||||
return UnzipIfNeeded(info.Path, cacheFile);
|
if (info.Path.StartsWith("http", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Downloading xmltv listings from {Path}", info.Path);
|
||||||
|
|
||||||
|
using var response = await _httpClientFactory.CreateClient(NamedClient.Default).GetAsync(info.Path, cancellationToken).ConfigureAwait(false);
|
||||||
|
await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await using var stream = new FileStream(info.Path, FileMode.Open, FileAccess.Read, FileShare.Read, IODefaults.FileStreamBufferSize, FileOptions.Asynchronous);
|
||||||
|
|
||||||
|
return await UnzipIfNeededAndCopy(info.Path, stream, cacheFile, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string UnzipIfNeeded(ReadOnlySpan<char> originalUrl, string file)
|
private async Task<string> UnzipIfNeededAndCopy(string originalUrl, Stream stream, string file, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
ReadOnlySpan<char> ext = Path.GetExtension(originalUrl.LeftPart('?'));
|
int index = originalUrl.IndexOf('?', StringComparison.CurrentCulture);
|
||||||
|
string ext = Path.GetExtension(index > -1 ? originalUrl.Remove(index) : originalUrl);
|
||||||
|
|
||||||
|
await using var fileStream = new FileStream(file, FileMode.CreateNew, FileAccess.Write, FileShare.None, IODefaults.CopyToBufferSize, FileOptions.Asynchronous);
|
||||||
|
|
||||||
if (ext.Equals(".gz", StringComparison.OrdinalIgnoreCase))
|
if (ext.Equals(".gz", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string tempFolder = ExtractGz(file);
|
using var reader = new GZipStream(stream, CompressionMode.Decompress);
|
||||||
return FindXmlFile(tempFolder);
|
await reader.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error extracting from gz file {File}", file);
|
_logger.LogError(ex, "Error extracting from gz file {File}", originalUrl);
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
string tempFolder = ExtractFirstFileFromGz(file);
|
|
||||||
return FindXmlFile(tempFolder);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError(ex, "Error extracting from zip file {File}", file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
await stream.CopyToAsync(fileStream, cancellationToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ExtractFirstFileFromGz(string file)
|
|
||||||
{
|
|
||||||
using (var stream = File.OpenRead(file))
|
|
||||||
{
|
|
||||||
string tempFolder = GetTempFolderPath(stream);
|
|
||||||
Directory.CreateDirectory(tempFolder);
|
|
||||||
|
|
||||||
_zipClient.ExtractFirstFileFromGz(stream, tempFolder, "data.xml");
|
|
||||||
|
|
||||||
return tempFolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string ExtractGz(string file)
|
|
||||||
{
|
|
||||||
using (var stream = File.OpenRead(file))
|
|
||||||
{
|
|
||||||
string tempFolder = GetTempFolderPath(stream);
|
|
||||||
Directory.CreateDirectory(tempFolder);
|
|
||||||
|
|
||||||
_zipClient.ExtractAllFromGz(stream, tempFolder, true);
|
|
||||||
|
|
||||||
return tempFolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GetTempFolderPath(Stream stream)
|
|
||||||
{
|
|
||||||
#pragma warning disable CA5351
|
|
||||||
using var md5 = MD5.Create();
|
|
||||||
#pragma warning restore CA5351
|
|
||||||
var checksum = Convert.ToHexString(md5.ComputeHash(stream));
|
|
||||||
stream.Position = 0;
|
|
||||||
return Path.Combine(_config.ApplicationPaths.TempDirectory, checksum);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string FindXmlFile(string directory)
|
|
||||||
{
|
|
||||||
return _fileSystem.GetFiles(directory, true)
|
|
||||||
.Where(i => string.Equals(i.Extension, ".xml", StringComparison.OrdinalIgnoreCase))
|
|
||||||
.Select(i => i.FullName)
|
|
||||||
.FirstOrDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
public async Task<IEnumerable<ProgramInfo>> GetProgramsAsync(ListingsProviderInfo info, string channelId, DateTime startDateUtc, DateTime endDateUtc, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(channelId))
|
if (string.IsNullOrWhiteSpace(channelId))
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
#pragma warning disable CS1591
|
|
||||||
|
|
||||||
using System.IO;
|
|
||||||
|
|
||||||
namespace MediaBrowser.Model.IO
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Interface IZipClient.
|
|
||||||
/// </summary>
|
|
||||||
public interface IZipClient
|
|
||||||
{
|
|
||||||
void ExtractAllFromGz(Stream source, string targetPath, bool overwriteExistingFiles);
|
|
||||||
|
|
||||||
void ExtractFirstFileFromGz(Stream source, string targetPath, string defaultFileName);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user