Skip to content

Commit

Permalink
release 0.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
tmm360 committed Jul 23, 2023
2 parents f47ce8d + ba131c7 commit b34d8af
Show file tree
Hide file tree
Showing 8 changed files with 1,211 additions and 4 deletions.
29 changes: 29 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true

#### Define style ####

# All files
[*]
indent_style = space

# C# Project, JS and CSS files
[*.{csproj,js,ts,css,scss}]
indent_size = 2

#### Suppress warnings ####

# C# files
[*.cs]

# CA1031: Catch general exception types for process next video in case of some error
dotnet_diagnostic.CA1031.severity = none

# CA1054: Uri parameters should not be strings
dotnet_diagnostic.CA1054.severity = none # This library needs also strings

# CA1056: Uri properties should not be strings
dotnet_diagnostic.CA1056.severity = none # This library needs also strings

# CA2234: Pass system uri objects instead of strings
dotnet_diagnostic.CA2234.severity = none
1 change: 1 addition & 0 deletions UniversalFiles.sln
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{C5B37A36-A
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{06B15DCC-1EB3-49DE-B2FD-F2B8CD112B7A}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
.gitignore = .gitignore
LICENSE = LICENSE
README.md = README.md
Expand Down
247 changes: 247 additions & 0 deletions src/UniversalFiles/UniversalFile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Copyright 2023-present Etherna Sagl
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace Etherna.UniversalFiles
{
public class UniversalFile
{
// Fields.
private (byte[], Encoding?)? onlineResourceCache;

// Constructor.
public UniversalFile(
UniversalUri fileUri,
IHttpClientFactory httpClientFactory)
{
FileUri = fileUri;
HttpClientFactory = httpClientFactory;
}

// Properties.
public UniversalUri FileUri { get; }

// Protected properties.
protected IHttpClientFactory HttpClientFactory { get; }

// Methods.
public void ClearOnlineCache() => onlineResourceCache = null;

public async Task<bool> ExistsAsync(
bool useCacheIfOnline = false,
UniversalUriKind allowedUriKinds = UniversalUriKind.All,
string? baseDirectory = null)
{
// Use cache if enabled and available.
if (useCacheIfOnline && onlineResourceCache != null)
return true;

var (absoluteUri, absoluteUriKind) = FileUri.ToAbsoluteUri(allowedUriKinds, baseDirectory);
switch (absoluteUriKind)
{
case UniversalUriKind.LocalAbsolute:
return File.Exists(absoluteUri) || Directory.Exists(absoluteUri);

case UniversalUriKind.OnlineAbsolute:
// Try to get resource byte size with an HEAD request.
var byteSyze = await TryGetOnlineByteSizeWithHeadRequestAsync(absoluteUri).ConfigureAwait(false);
if (byteSyze != null)
return true;

// Otherwise, try to download it.
var onlineContent = await TryGetOnlineAsByteArrayAsync(absoluteUri).ConfigureAwait(false);
if (onlineContent != null && useCacheIfOnline)
onlineResourceCache = onlineContent;

return onlineContent != null;

default: throw new InvalidOperationException("Invalid absolute uri kind. It should be well defined and absolute");
}
}

public async Task<long> GetByteSizeAsync(
bool useCacheIfOnline = false,
UniversalUriKind allowedUriKinds = UniversalUriKind.All,
string? baseDirectory = null)
{
// Use cache if enabled and available.
if (useCacheIfOnline && onlineResourceCache != null)
return onlineResourceCache.Value.Item1.LongLength;

// Get resource size.
var (absoluteUri, absoluteUriKind) = FileUri.ToAbsoluteUri(allowedUriKinds, baseDirectory);
switch (absoluteUriKind)
{
case UniversalUriKind.LocalAbsolute:
return new FileInfo(absoluteUri).Length;

case UniversalUriKind.OnlineAbsolute:
// Try to get resource byte size with an HEAD request.
var byteSyze = await TryGetOnlineByteSizeWithHeadRequestAsync(absoluteUri).ConfigureAwait(false);
if (byteSyze.HasValue)
return byteSyze.Value;

// Otherwise, try to download it.
var onlineResource = await TryGetOnlineAsByteArrayAsync(absoluteUri).ConfigureAwait(false) ??
throw new IOException($"Can't retrieve online resource at {absoluteUri}");

if (useCacheIfOnline)
onlineResourceCache = onlineResource;

return onlineResource.Item1.LongLength;

default: throw new InvalidOperationException("Invalid absolute uri kind. It should be well defined and absolute");
}
}

public async Task<(byte[] ByteArray, Encoding? Encoding)> ReadAsByteArrayAsync(
bool useCacheIfOnline = false,
UniversalUriKind allowedUriKinds = UniversalUriKind.All,
string? baseDirectory = null)
{
// Use cache if enabled and available.
if (useCacheIfOnline && onlineResourceCache != null)
return onlineResourceCache.Value;

// Get resource.
var (absoluteUri, absoluteUriKind) = FileUri.ToAbsoluteUri(allowedUriKinds, baseDirectory);
switch (absoluteUriKind)
{
case UniversalUriKind.LocalAbsolute:
return (await File.ReadAllBytesAsync(absoluteUri).ConfigureAwait(false), null);

case UniversalUriKind.OnlineAbsolute:
var onlineResource = await TryGetOnlineAsByteArrayAsync(absoluteUri).ConfigureAwait(false) ??
throw new IOException($"Can't retrieve online resource at {absoluteUri}");

if (useCacheIfOnline)
onlineResourceCache = onlineResource;

return onlineResource;

default: throw new InvalidOperationException("Invalid absolute uri kind. It should be well defined and absolute");
}
}

public async Task<(Stream Stream, Encoding? Encoding)> ReadAsStreamAsync(
UniversalUriKind allowedUriKinds = UniversalUriKind.All,
string? baseDirectory = null)
{
// Get resource.
var (absoluteUri, absoluteUriKind) = FileUri.ToAbsoluteUri(allowedUriKinds, baseDirectory);
return absoluteUriKind switch
{
UniversalUriKind.LocalAbsolute => (File.OpenRead(absoluteUri), null),

UniversalUriKind.OnlineAbsolute => await TryGetOnlineAsStreamAsync(absoluteUri).ConfigureAwait(false)
?? throw new IOException($"Can't retrieve online resource at {absoluteUri}"),

_ => throw new InvalidOperationException("Invalid absolute uri kind. It should be well defined and absolute"),
};
}

public async Task<string> ReadAsStringAsync(
bool useCacheIfOnline = false,
UniversalUriKind allowedUriKinds = UniversalUriKind.All,
string? baseDirectory = null)
{
var (content, encoding) = await ReadAsByteArrayAsync(useCacheIfOnline, allowedUriKinds, baseDirectory).ConfigureAwait(false);
encoding ??= Encoding.UTF8;
return encoding.GetString(content);
}

public string? TryGetFileName()
{
if (FileUri.OriginalUri.EndsWith('/') ||
FileUri.OriginalUri.EndsWith('\\'))
return null;
return FileUri.OriginalUri.Split('/', '\\').Last();
}

// Helpers.
private async Task<(byte[], Encoding?)?> TryGetOnlineAsByteArrayAsync(
string onlineAbsoluteUri)
{
var result = await TryGetOnlineAsStreamAsync(onlineAbsoluteUri).ConfigureAwait(false);
if (result is null)
return null;

var (contentStream, encoding) = result.Value;
var byteArrayContent = contentStream.ToArray();
await contentStream.DisposeAsync().ConfigureAwait(false);

return (byteArrayContent, encoding);
}

private async Task<(MemoryStream, Encoding?)?> TryGetOnlineAsStreamAsync(
string onlineAbsoluteUri)
{
try
{
using var httpClient = HttpClientFactory.CreateClient();
using var response = await httpClient.GetAsync(onlineAbsoluteUri).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
return null;

// Get content with encoding.
using var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
Encoding? contentEncoding = null;

// Copy stream to memory stream.
var memoryStream = new MemoryStream();
await contentStream.CopyToAsync(memoryStream).ConfigureAwait(false);
memoryStream.Position = 0;

// Try to extract the encoding from the Content-Type header.
if (response.Content.Headers.ContentType?.CharSet != null)
{
try { contentEncoding = Encoding.GetEncoding(response.Content.Headers.ContentType.CharSet); }
catch (ArgumentException) { }
}

return (memoryStream, contentEncoding);
}
catch
{
return null;
}
}

private async Task<long?> TryGetOnlineByteSizeWithHeadRequestAsync(string absoluteUri)
{
try
{
using var httpClient = HttpClientFactory.CreateClient();
using var httpRequestMessage = new HttpRequestMessage(HttpMethod.Head, absoluteUri);
using var response = await httpClient.SendAsync(httpRequestMessage).ConfigureAwait(false);

if (response.Headers.TryGetValues("Content-Length", out var values))
{
string contentLength = values.GetEnumerator().Current;
if (long.TryParse(contentLength, out var byteSize))
return byteSize;
}
}
catch { }

return default;
}
}
}
2 changes: 1 addition & 1 deletion src/UniversalFiles/UniversalFiles.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="GitVersion.MsBuild" Version="5.12.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Loading

0 comments on commit b34d8af

Please sign in to comment.