File: FileSystem\FileSystem.shared.cs
Web Access
Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials)
#nullable enable
using System;
using System.IO;
using System.Threading.Tasks;
 
namespace Microsoft.Maui.Storage
{
	/// <summary>
	/// Provides an easy way to access the locations for device folders.
	/// </summary>
	public interface IFileSystem
	{
		/// <summary>
		/// Gets the location where temporary data can be stored.
		/// </summary>
		/// <remarks>This location usually is not visible to the user, is not backed up, and may be cleared by the operating system at any time.</remarks>
		string CacheDirectory { get; }
 
		/// <summary>
		/// Gets the location where app data can be stored.
		/// </summary>
		/// <remarks>This location usually is not visible to the user, and is backed up.</remarks>
		string AppDataDirectory { get; }
 
		/// <summary>
		/// Opens a stream to a file contained within the app package.
		/// </summary>
		/// <param name="filename">The name of the file (excluding the path) to load from the app package.</param>
		/// <returns>A <see cref="Stream"/> containing the (read-only) file data.</returns>
		Task<Stream> OpenAppPackageFileAsync(string filename);
 
		/// <summary>
		/// Determines whether or not a file exists in the app package.
		/// </summary>
		/// <param name="filename">The name of the file (excluding the path) to load from the app package.</param>
		/// <returns><see langword="true"/> when the specified file exists in the app package, otherwise <see langword="false"/>.</returns>
		Task<bool> AppPackageFileExistsAsync(string filename);
	}
 
	/// <summary>
	/// Provides an easy way to access the locations for device folders.
	/// </summary>
	public static class FileSystem
	{
		/// <summary>
		/// Gets the location where temporary data can be stored.
		/// </summary>
		/// <remarks>This location usually is not visible to the user, is not backed up, and may be cleared by the operating system at any time.</remarks>
		public static string CacheDirectory
			=> Current.CacheDirectory;
 
		/// <summary>
		/// Gets the location where app data can be stored.
		/// </summary>
		/// <remarks>This location usually is not visible to the user, and is backed up.</remarks>
		public static string AppDataDirectory
			=> Current.AppDataDirectory;
 
		/// <summary>
		/// Opens a stream to a file contained within the app package.
		/// </summary>
		/// <param name="filename">The name of the file (excluding the path) to load from the app package.</param>
		/// <returns>A <see cref="Stream"/> containing the (read-only) file data.</returns>
		public static Task<Stream> OpenAppPackageFileAsync(string filename)
			=> Current.OpenAppPackageFileAsync(filename);
 
		/// <summary>
		/// Determines whether or not a file exists in the app package.
		/// </summary>
		/// <param name="filename">The path of the file (relative to the app package) to check the existence of.</param>
		/// <returns><see langword="true"/> when the specified file exists in the app package, otherwise <see langword="false"/>.</returns>
		public static Task<bool> AppPackageFileExistsAsync(string filename)
			=> Current.AppPackageFileExistsAsync(filename);
 
		static IFileSystem? currentImplementation;
 
		/// <summary>
		/// Provides the default implementation for static usage of this API.
		/// </summary>
		public static IFileSystem Current =>
			currentImplementation ??= new FileSystemImplementation();
 
		internal static void SetCurrent(IFileSystem? implementation) =>
			currentImplementation = implementation;
	}
 
	/// <summary>
	/// Concrete implementation of the <see cref="IFileSystem"/> APIs.
	/// </summary>
	public partial class FileSystemImplementation
	{
		/// <inheritdoc cref="IFileSystem.CacheDirectory"/>
		public string CacheDirectory
			=> PlatformCacheDirectory;
 
		/// <inheritdoc cref="IFileSystem.AppDataDirectory"/>
		public string AppDataDirectory
			=> PlatformAppDataDirectory;
 
		/// <inheritdoc cref="IFileSystem.OpenAppPackageFileAsync(string)"/>
		public Task<Stream> OpenAppPackageFileAsync(string filename)
			=> PlatformOpenAppPackageFileAsync(filename);
 
		/// <inheritdoc cref="IFileSystem.AppPackageFileExistsAsync(string)"/>
		public Task<bool> AppPackageFileExistsAsync(string filename)
			=> PlatformAppPackageFileExistsAsync(filename);
	}
 
	static class FileMimeTypes
	{
		internal const string All = "*/*";
 
		internal const string ImageAll = "image/*";
		internal const string ImagePng = "image/png";
		internal const string ImageJpg = "image/jpeg";
 
		internal const string VideoAll = "video/*";
 
		internal const string EmailMessage = "message/rfc822";
 
		internal const string Pdf = "application/pdf";
 
		internal const string TextPlain = "text/plain";
 
		internal const string OctetStream = "application/octet-stream";
	}
 
	static class FileExtensions
	{
		internal const string Png = ".png";
		internal const string Jpg = ".jpg";
		internal const string Jpeg = ".jpeg";
		internal const string Gif = ".gif";
		internal const string Bmp = ".bmp";
 
		internal const string Avi = ".avi";
		internal const string Flv = ".flv";
		internal const string Gifv = ".gifv";
		internal const string Mp4 = ".mp4";
		internal const string M4v = ".m4v";
		internal const string Mpg = ".mpg";
		internal const string Mpeg = ".mpeg";
		internal const string Mp2 = ".mp2";
		internal const string Mkv = ".mkv";
		internal const string Mov = ".mov";
		internal const string Qt = ".qt";
		internal const string Wmv = ".wmv";
 
		internal const string Pdf = ".pdf";
 
		internal static string[] AllImage =>
			new[] { Png, Jpg, Jpeg, Gif, Bmp };
 
		internal static string[] AllJpeg =>
			new[] { Jpg, Jpeg };
 
		internal static string[] AllVideo =>
			new[] { Mp4, Mov, Avi, Wmv, M4v, Mpg, Mpeg, Mp2, Mkv, Flv, Gifv, Qt };
 
		internal static string Clean(string extension, bool trimLeadingPeriod = false)
		{
			if (string.IsNullOrWhiteSpace(extension))
				return string.Empty;
 
			extension = extension.TrimStart('*');
			extension = extension.TrimStart('.');
 
			if (!trimLeadingPeriod)
				extension = "." + extension;
 
			return extension;
		}
	}
 
	/// <summary>
	/// A representation of a file and its content type.
	/// </summary>
	public abstract partial class FileBase
	{
		internal const string DefaultContentType = FileMimeTypes.OctetStream;
 
		string? contentType;
 
		// The caller must setup FullPath at least!!!
		internal FileBase()
		{
		}
 
		internal FileBase(string fullPath)
		{
			if (fullPath == null)
				throw new ArgumentNullException(nameof(fullPath));
			if (string.IsNullOrWhiteSpace(fullPath))
				throw new ArgumentException("The file path cannot be an empty string.", nameof(fullPath));
			if (string.IsNullOrWhiteSpace(Path.GetFileName(fullPath)))
				throw new ArgumentException("The file path must be a file path.", nameof(fullPath));
 
			FullPath = fullPath;
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="FileBase"/> class from an existing instance.
		/// </summary>
		/// <param name="file">A <see cref="FileBase"/> instance that will be used to clone.</param>
		public FileBase(FileBase file)
		{
			FullPath = file.FullPath;
			ContentType = file.ContentType;
			FileName = file.FileName;
			PlatformInit(file);
		}
 
		internal FileBase(string fullPath, string contentType)
			: this(fullPath)
		{
			FullPath = fullPath;
			ContentType = contentType;
		}
 
		/// <summary>
		/// Gets the full path and filename.
		/// </summary>
		public string FullPath { get; internal set; } = null!;
 
		/// <summary>
		/// Gets or sets the file's content type as a MIME type (e.g.: <c>image/png</c>).
		/// </summary>
		public string ContentType
		{
			get => GetContentType();
			set => contentType = value;
		}
 
		internal string GetContentType()
		{
			// try the provided type
			if (!string.IsNullOrWhiteSpace(contentType))
				return contentType!;
 
			// try get from the file extension
			var ext = Path.GetExtension(FullPath);
			if (!string.IsNullOrWhiteSpace(ext))
			{
				var content = PlatformGetContentType(ext);
				if (!string.IsNullOrWhiteSpace(content))
					return content;
			}
 
			return DefaultContentType;
		}
 
		string? fileName;
 
		/// <summary>
		/// Gets or sets the filename for this file.
		/// </summary>
		public string FileName
		{
			get => GetFileName();
			set => fileName = value;
		}
 
		internal string GetFileName()
		{
			// try the provided file name
			if (!string.IsNullOrWhiteSpace(fileName))
				return fileName!;
 
			// try get from the path
			if (!string.IsNullOrWhiteSpace(FullPath))
				return Path.GetFileName(FullPath);
 
			// this should never happen as the path is validated in the constructor
			throw new InvalidOperationException($"Unable to determine the file name from '{FullPath}'.");
		}
 
		/// <summary>
		/// Opens a <see cref="Stream"/> to the corresponding file on the filesystem.
		/// </summary>
		/// <returns>A <see cref="Stream"/> containing the file data.</returns>
		public Task<Stream> OpenReadAsync()
			=> PlatformOpenReadAsync();
	}
 
	/// <summary>
	/// A representation of a file, that is read-only, and its content type.
	/// </summary>
	public class ReadOnlyFile : FileBase
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ReadOnlyFile"/> class from a file path.
		/// </summary>
		/// <param name="fullPath">Full file path to the corresponding file on the filesystem.</param>
		public ReadOnlyFile(string fullPath)
			: base(fullPath)
		{
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="ReadOnlyFile"/> class from a file path, explicitly specifying the content type.
		/// </summary>
		/// <param name="fullPath">Full file path to the corresponding file on the filesystem.</param>
		/// <param name="contentType">Content type (MIME type) of the file (e.g.: <c>image/png</c>).</param>
		public ReadOnlyFile(string fullPath, string contentType)
			: base(fullPath, contentType)
		{
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="ReadOnlyFile"/> class from an existing instance.
		/// </summary>
		/// <param name="file">A <see cref="FileBase"/> instance that will be used to clone.</param>
		public ReadOnlyFile(FileBase file)
			: base(file)
		{
		}
	}
 
	/// <summary>
	/// A representation of a file, as a result of a pick action by the user, and its content type.
	/// </summary>
	public partial class FileResult : FileBase
	{
		// The caller must setup FullPath at least!!!
		internal FileResult()
		{
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="FileResult"/> class from a file path.
		/// </summary>
		/// <param name="fullPath">Full file path to the corresponding file on the filesystem.</param>
		public FileResult(string fullPath)
			: base(fullPath)
		{
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="FileResult"/> class from a file path, explicitly specifying the content type.
		/// </summary>
		/// <param name="fullPath">Full file path to the corresponding file on the filesystem.</param>
		/// <param name="contentType">Content type (MIME type) of the file (e.g.: <c>image/png</c>).</param>
		public FileResult(string fullPath, string contentType)
			: base(fullPath, contentType)
		{
		}
 
		/// <summary>
		/// Initializes a new instance of the <see cref="FileResult"/> class from an existing instance.
		/// </summary>
		/// <param name="file">A <see cref="FileBase"/> instance that will be used to clone.</param>
		public FileResult(FileBase file)
			: base(file)
		{
		}
	}
}