Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions Softeq.XToolkit.Common.Droid/Files/DroidStorageInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Developed by Softeq Development Corporation
// http://www.softeq.com

using System.Collections.Generic;
using Android.OS;
using Java.IO;
using Softeq.XToolkit.Common.Files;

namespace Softeq.XToolkit.Common.Droid.Files
{
public class DroidStorageInfoProvider : IStorageInfoProvider
{
/// <inheritdoc />
/// <remarks>
/// More modern implementation via <see cref="T:Android.OS.Storage.StorageManager"/> yon can see here:
/// <see cref="T:Softeq.XToolkit.WhiteLabel.Droid.Providers.ModernDroidStorageInfoProvider"/>.
/// </remarks>
public List<StorageInfo> Current
{
get
{
var directory = Environment.IsExternalStorageEmulated
? Environment.ExternalStorageDirectory!
: Environment.DataDirectory;

var internalStorageInfo = GetInfoByDirectory(StorageType.Internal, directory!);

return new List<StorageInfo> { internalStorageInfo };
}
}

private static StorageInfo GetInfoByDirectory(StorageType type, File directory)
{
var stats = new StatFs(directory.Path);
return new StorageInfo(type, (ulong)stats.TotalBytes, (ulong)stats.AvailableBytes);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
<Compile Include="Converters\VisibilityConverter.cs" />
<Compile Include="DroidMainThreadExecutor.cs" />
<Compile Include="Extensions\ContextExtensions.cs" />
<Compile Include="Files\DroidStorageInfoProvider.cs" />
<Compile Include="Permissions\IPermissionRequestHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Extensions\TextViewExtensions.cs" />
Expand Down
115 changes: 115 additions & 0 deletions Softeq.XToolkit.Common.iOS/Files/IosStorageInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Developed by Softeq Development Corporation
// http://www.softeq.com

using System;
using System.Collections.Generic;
using System.Diagnostics;
using Foundation;
using ObjCRuntime;
using Softeq.XToolkit.Common.Files;
using UIKit;

namespace Softeq.XToolkit.Common.iOS.Files
{
public class IosStorageInfoProvider : IStorageInfoProvider
{
/// <inheritdoc />
public List<StorageInfo> Current
{
get
{
var homePath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
var fileSystemAttributes = GetFileSystemAttributesForPath(homePath);

StorageInfo info;

Debug.WriteLine($"System Total: {fileSystemAttributes.Size}");
if (UIDevice.CurrentDevice.CheckSystemVersion(11, 0))
{
var volumeCapacities = GetVolumeAvailableCapacities(homePath);
info = new StorageInfo(StorageType.Internal, fileSystemAttributes.Size, volumeCapacities.AvailableCapacityForImportantUsage);

Debug.WriteLine($"AvailableCapacity: {volumeCapacities.AvailableCapacity}");
Debug.WriteLine($"AvailableCapacityForImportantUsage: {volumeCapacities.AvailableCapacityForImportantUsage}");
Debug.WriteLine($"AvailableCapacityForOpportunisticUsage: {volumeCapacities.AvailableCapacityForOpportunisticUsage}");
}
else
{
Debug.WriteLine($"System Free: {fileSystemAttributes.FreeSize}");
info = new StorageInfo(StorageType.Internal, fileSystemAttributes.Size, fileSystemAttributes.FreeSize);
}

return new List<StorageInfo> { info };
}
}

public NSFileSystemAttributes GetFileSystemAttributesForPath(string path)
{
var attributes = NSFileManager.DefaultManager.GetFileSystemAttributes(path, out NSError error);

if (error != null)
{
throw new NSErrorException(error);
}

return attributes;
}

/// <summary>
/// Total available capacity in bytes for "Important" resources,
/// including space expected to be cleared by purging non-essential and cached resources.
///
/// "Important" means something that the user or application clearly expects to be present on the local system,
/// but is ultimately replaceable. This would include items that the user has explicitly requested via the UI,
/// and resources that an application requires in order to provide functionality.
///
/// Examples:
/// A video that the user has explicitly requested to watch but has not yet finished watching or
/// an audio file that the user has requested to download.
/// This value should not be used in determining if there is room for an irreplaceable resource.
/// In the case of irreplaceable resources, always attempt to save the resource regardless of available capacity and
/// handle failure as gracefully as possible.
/// </summary>
/// <remarks>
/// More details:
/// <see href="https://developer.apple.com/documentation/foundation/nsurlresourcekey/checking_volume_storage_capacity?language=objc" />.
/// </remarks>
/// <param name="path">Target path. Default is file system root.</param>
/// <returns>
/// Available capacity values:
/// - AvailableCapacity - available capacity in bytes, minimal.
/// - AvailableCapacityForImportantUsage - capacity in bytes for storing important resources, maximum available space.
/// - AvailableCapacityForOpportunisticUsage - capacity in bytes for storing nonessential resources.
/// </returns>
/// <exception cref="NSErrorException">Native error during request info from the system.</exception>
[Introduced(PlatformName.iOS, 11, 0)]
public (ulong AvailableCapacity, ulong AvailableCapacityForImportantUsage, ulong AvailableCapacityForOpportunisticUsage)
GetVolumeAvailableCapacities(string path = "/")
{
var keys = new[]
{
NSUrl.VolumeAvailableCapacityKey,
NSUrl.VolumeAvailableCapacityForImportantUsageKey,
NSUrl.VolumeAvailableCapacityForOpportunisticUsageKey
};

var dict = NSUrl.FromFilename(path).GetResourceValues(keys, out NSError error);
if (error != null)
{
throw new NSErrorException(error);
}

var availableCapacity = GetNumber(dict, NSUrl.VolumeAvailableCapacityKey);
var capacityForImportantUsage = GetNumber(dict, NSUrl.VolumeAvailableCapacityForImportantUsageKey);
var capacityForOpportunisticUsage = GetNumber(dict, NSUrl.VolumeAvailableCapacityForOpportunisticUsageKey);

return (availableCapacity, capacityForImportantUsage, capacityForOpportunisticUsage);
}

protected static ulong GetNumber(NSDictionary dict, NSString key)
{
var value = dict.ObjectForKey(key) as NSNumber;
return value?.UInt64Value ?? 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
<Reference Include="Xamarin.iOS" />
</ItemGroup>
<ItemGroup>
<Compile Include="Files\IosStorageInfoProvider.cs" />
<Compile Include="IosMainThreadExecutor.cs" />
<Compile Include="TextFilters\ForbiddenCharsFilter.cs" />
<Compile Include="TextFilters\ITextFilter.cs" />
Expand Down
19 changes: 19 additions & 0 deletions Softeq.XToolkit.Common/Files/IStorageInfoProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Developed by Softeq Development Corporation
// http://www.softeq.com

using System.Collections.Generic;
using System.Threading.Tasks;

namespace Softeq.XToolkit.Common.Files
{
/// <summary>
/// Provides info about available storages.
/// </summary>
public interface IStorageInfoProvider
{
/// <summary>
/// Gets info about available storages.
/// </summary>
List<StorageInfo> Current { get; }
}
}
65 changes: 65 additions & 0 deletions Softeq.XToolkit.Common/Files/StorageInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Developed by Softeq Development Corporation
// http://www.softeq.com

namespace Softeq.XToolkit.Common.Files
{
/// <summary>
/// Contains info about storage.
/// </summary>
public readonly struct StorageInfo
{
/// <summary>
/// Initializes a new instance of the <see cref="StorageInfo"/> struct.
/// </summary>
/// <param name="type">Type of storage.</param>
/// <param name="totalBytes">Total storage bytes.</param>
/// <param name="freeBytes">Free storage bytes.</param>
public StorageInfo(StorageType type, ulong totalBytes, ulong freeBytes)
{
Type = type;
TotalBytes = totalBytes;
FreeBytes = freeBytes;
}

/// <summary>
/// Gets storage type.
/// </summary>
public StorageType Type { get; }

/// <summary>
/// Gets total storage bytes.
/// </summary>
public ulong TotalBytes { get; }

/// <summary>
/// Gets available free storage bytes.
/// </summary>
public ulong FreeBytes { get; }

/// <inheritdoc />
public override string ToString() => $"Type: {Type}, Total: {TotalBytes}, Free: {FreeBytes}";
}

/// <summary>
/// Storage type.
/// </summary>
#pragma warning disable SA1201
public enum StorageType
#pragma warning restore SA1201
{
/// <summary>
/// Unknown storage.
/// </summary>
Unknown = 0,

/// <summary>
/// Internal storage.
/// </summary>
Internal = 1,

/// <summary>
/// External storage.
/// </summary>
External = 2
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Developed by Softeq Development Corporation
// http://www.softeq.com

using System;
using System.Collections.Generic;
using System.Linq;
using Android.App.Usage;
using Android.Content;
using Android.OS;
using Android.OS.Storage;
using Java.IO;
using Java.Util;
using Softeq.XToolkit.Common.Extensions;
using Softeq.XToolkit.Common.Files;

namespace Softeq.XToolkit.WhiteLabel.Droid.Providers
{
public class ModernDroidStorageInfoProvider : IStorageInfoProvider
{
private readonly IContextProvider _contextProvider;

public ModernDroidStorageInfoProvider(IContextProvider contextProvider)
{
_contextProvider = contextProvider;
}

/// <inheritdoc />
public List<StorageInfo> Current
{
get
{
var storageManager = StorageManager.FromContext(_contextProvider.AppContext);
if (storageManager == null)
{
throw new InvalidOperationException($"Can't resolve '{nameof(StorageManager)}'");
}

var storageStatsManager = _contextProvider.AppContext.GetSystemService(Context.StorageStatsService) as StorageStatsManager;
if (storageStatsManager == null)
{
throw new InvalidOperationException($"Can't resolve '{nameof(StorageStatsManager)}'");
}

return _contextProvider.AppContext.GetExternalFilesDirs(null)
.EmptyIfNull()
.Select(directory => GetInfoByDirectory(directory, storageManager, storageStatsManager))
.ToList();
}
}

// YP: Workaround over the storageManager.getStorageVolumes + getDirectory (API 30)
// https://stackoverflow.com/a/57126727/5925490
private static StorageInfo GetInfoByDirectory(
File directory,
StorageManager storageManager,
StorageStatsManager storageStatsManager)
{
var type = StorageType.Unknown;
var storageVolume = storageManager.GetStorageVolume(directory);

if (storageVolume != null)
{
type = storageVolume.IsRemovable ? StorageType.External : StorageType.Internal;

// YP: skip non-primary volumes, because should be used `storageVolume.getStorageUuid` (API 31)
if (storageVolume.IsPrimary)
{
return GetInfoByVolume(type, storageVolume.Uuid, storageStatsManager);
}
}

var stats = new StatFs(directory.Path);
return new StorageInfo(type, (ulong)stats.TotalBytes, (ulong)stats.AvailableBytes);
}

private static StorageInfo GetInfoByVolume(
StorageType type,
string? storageVolumeUuid,
StorageStatsManager storageStatsManager)
{
var storageUuid = string.IsNullOrEmpty(storageVolumeUuid)
? StorageManager.UuidDefault
: UUID.FromString(storageVolumeUuid!);

if (storageUuid == null)
{
throw new InvalidOperationException($"Invalid storage volume UUID: '{storageVolumeUuid}'.");
}

var totalBytes = storageStatsManager.GetTotalBytes(storageUuid);
var freeBytes = storageStatsManager.GetFreeBytes(storageUuid);

return new StorageInfo(type, (ulong)totalBytes, (ulong)freeBytes);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<Compile Include="Navigation\ViewType.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\ContextProvider.cs" />
<Compile Include="Providers\ModernDroidStorageInfoProvider.cs" />
<Compile Include="Services\DroidAppInfoService.cs" />
<Compile Include="Services\DroidToastService.cs" />
<Compile Include="Services\KeyboardService.cs" />
Expand Down