Added Storage access framework, DiscUtils More of Zio, Fixed Dispose and internal function UnixPath.SetPath

This commit is contained in:
Mike Nolan 2024-07-22 18:44:49 -05:00
parent 60a1f8e7d3
commit 1f3f1c9b89
12 changed files with 1117 additions and 13 deletions

View File

@ -15,6 +15,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tesses.VirtualFilesystem.Lo
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tesses.VirtualFilesystem.Zio", "Tesses.VirtualFilesystem.Zio\Tesses.VirtualFilesystem.Zio.csproj", "{F30FD55D-194C-437E-AAD2-6E1E0FEEF1C2}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tesses.VirtualFilesystem.Zio", "Tesses.VirtualFilesystem.Zio\Tesses.VirtualFilesystem.Zio.csproj", "{F30FD55D-194C-437E-AAD2-6E1E0FEEF1C2}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tesses.VirtualFilesystem.DiscUtils", "Tesses.VirtualFilesystem.DiscUtils\Tesses.VirtualFilesystem.DiscUtils.csproj", "{D399E2F3-B852-443E-8117-4B715EB84953}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tesses.VirtualFilesystem.Saf", "Tesses.VirtualFilesystem.Saf\Tesses.VirtualFilesystem.Saf.csproj", "{61FADB48-263C-494E-A564-A477B0D8C3F3}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -48,5 +52,13 @@ Global
{F30FD55D-194C-437E-AAD2-6E1E0FEEF1C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {F30FD55D-194C-437E-AAD2-6E1E0FEEF1C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F30FD55D-194C-437E-AAD2-6E1E0FEEF1C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {F30FD55D-194C-437E-AAD2-6E1E0FEEF1C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F30FD55D-194C-437E-AAD2-6E1E0FEEF1C2}.Release|Any CPU.Build.0 = Release|Any CPU {F30FD55D-194C-437E-AAD2-6E1E0FEEF1C2}.Release|Any CPU.Build.0 = Release|Any CPU
{D399E2F3-B852-443E-8117-4B715EB84953}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D399E2F3-B852-443E-8117-4B715EB84953}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D399E2F3-B852-443E-8117-4B715EB84953}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D399E2F3-B852-443E-8117-4B715EB84953}.Release|Any CPU.Build.0 = Release|Any CPU
{61FADB48-263C-494E-A564-A477B0D8C3F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{61FADB48-263C-494E-A564-A477B0D8C3F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{61FADB48-263C-494E-A564-A477B0D8C3F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{61FADB48-263C-494E-A564-A477B0D8C3F3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View File

@ -488,7 +488,7 @@ namespace Tesses.VirtualFilesystem
} }
public interface IVirtualWatcher public interface IVirtualWatcher : IDisposable
{ {
event EventHandler<VirtualWatcherChangedArgs> Changed; event EventHandler<VirtualWatcherChangedArgs> Changed;
@ -527,6 +527,7 @@ namespace Tesses.VirtualFilesystem
/// in <see cref="Path"/>. /// in <see cref="Path"/>.
/// </summary> /// </summary>
bool IncludeSubdirectories { get; set; } bool IncludeSubdirectories { get; set; }
} }
public class VirtualWatcherRenamedEventArgs : VirtualWatcherChangedArgs public class VirtualWatcherRenamedEventArgs : VirtualWatcherChangedArgs
@ -871,7 +872,10 @@ namespace Tesses.VirtualFilesystem
{ {
pa.Append($"/{_parts[i]}"); pa.Append($"/{_parts[i]}");
} }
if(pa.Length > 0)
_path = pa.ToString(); _path = pa.ToString();
else
_path = "/";
} }
public UnixPath(UnixPath path1) public UnixPath(UnixPath path1)
{ {

View File

@ -17,9 +17,9 @@
<PackageId>Tesses.VirtualFilesystem.Base</PackageId> <PackageId>Tesses.VirtualFilesystem.Base</PackageId>
<Author>Mike Nolan</Author> <Author>Mike Nolan</Author>
<Company>Tesses</Company> <Company>Tesses</Company>
<Version>1.0.0</Version> <Version>1.0.1</Version>
<AssemblyVersion>1.0.0</AssemblyVersion> <AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.0</FileVersion> <FileVersion>1.0.1</FileVersion>
<Description>Another VirtualFilesystem for .NET</Description> <Description>Another VirtualFilesystem for .NET</Description>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>

View File

@ -0,0 +1,103 @@
using System;
using System.Collections.Generic;
using System.IO;
using DiscUtils;
using Tesses.VirtualFilesystem;
using Tesses.VirtualFilesystem.Filesystems;
namespace Tesses.VirtualFilesystem.Filesystems
{
public class DiscUtilsFilesystem : SyncFileSystem
{
IFileSystem fs;
public DiscUtilsFilesystem(IFileSystem fs)
{
this.fs = fs;
}
public override string ConvertPathFromUnixPath(UnixPath path)
{
return path.Path.Replace("/","\\");
}
public override UnixPath ConvertPathToUnixPath(string path)
{
return new UnixPath(path.Replace("\\","/"));
}
public override void CreateDirectory(UnixPath directory)
{
fs.CreateDirectory(ConvertPathFromUnixPath(directory));
}
public override void DeleteDirectory(UnixPath path)
{
fs.DeleteDirectory(ConvertPathFromUnixPath(path));
}
public override void DeleteFile(UnixPath path)
{
fs.DeleteFile(ConvertPathFromUnixPath(path));
}
public override bool DirectoryExists(UnixPath path)
{
return fs.DirectoryExists(ConvertPathFromUnixPath(path));
}
public override IEnumerable<UnixPath> EnumerateFileSystemEntries(UnixPath path)
{
foreach(var item in fs.GetFileSystemEntries(ConvertPathFromUnixPath(path)))
{
yield return ConvertPathToUnixPath(item);
}
}
public override bool FileExists(UnixPath path)
{
return fs.FileExists(ConvertPathFromUnixPath(path));
}
public override DateTime GetCreationTime(UnixPath path)
{
return fs.GetCreationTime(ConvertPathFromUnixPath(path));
}
public override DateTime GetLastAccessTime(UnixPath path)
{
return fs.GetLastAccessTime(ConvertPathFromUnixPath(path));
}
public override DateTime GetLastWriteTime(UnixPath path)
{
return fs.GetLastWriteTime(ConvertPathFromUnixPath(path));
}
public override void MoveDirectory(UnixPath src, UnixPath dest)
{
fs.MoveDirectory(ConvertPathFromUnixPath(src),ConvertPathFromUnixPath(dest));
}
public override void MoveFile(UnixPath src, UnixPath dest)
{
fs.MoveFile(ConvertPathFromUnixPath(src),ConvertPathFromUnixPath(dest));
}
public override Stream Open(UnixPath path, FileMode mode, FileAccess access, FileShare share)
{
return fs.OpenFile(ConvertPathFromUnixPath(path),mode,access);
}
public override void SetCreationTime(UnixPath path, DateTime time)
{
fs.SetCreationTime(ConvertPathFromUnixPath(path),time);
}
public override void SetLastAccessTime(UnixPath path, DateTime time)
{
fs.SetLastAccessTime(ConvertPathFromUnixPath(path),time);
}
public override void SetLastWriteTime(UnixPath path, DateTime time)
{
fs.SetLastWriteTime(ConvertPathFromUnixPath(path),time);
}
}
}

View File

@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Tesses.VirtualFilesystem.Extensions\Tesses.VirtualFilesystem.Extensions.csproj" />
<ProjectReference Include="..\Tesses.VirtualFilesystem\Tesses.VirtualFilesystem.csproj" />
<ProjectReference Include="..\Tesses.VirtualFilesystem.Base\Tesses.VirtualFilesystem.Base.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="DiscUtils" Version="0.16.13" />
</ItemGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>true</IsPackable>
<PackageId>Tesses.VirtualFilesystem.DiscUtils</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Description>Another VirtualFilesystem for .NET</Description>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>VFS, Filesystem, VirtualFilesystem</PackageTags>
<RepositoryUrl>https://gitlab.tesses.net/tesses50/tesses-vfs</RepositoryUrl>
<LangVersion>8.0</LangVersion>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
</Project>

View File

@ -165,6 +165,11 @@ namespace Tesses.VirtualFilesystem.Filesystems
public event EventHandler<VirtualWatcherChangedArgs> Deleted; public event EventHandler<VirtualWatcherChangedArgs> Deleted;
public event EventHandler<VirtualWatcherRenamedEventArgs> Renamed; public event EventHandler<VirtualWatcherRenamedEventArgs> Renamed;
public event EventHandler<ErrorEventArgs> Error; public event EventHandler<ErrorEventArgs> Error;
public void Dispose()
{
this.watcher.Dispose();
}
} }
public override IVirtualWatcher WatchDirectory(UnixPath dir) public override IVirtualWatcher WatchDirectory(UnixPath dir)

View File

@ -17,9 +17,9 @@
<PackageId>Tesses.VirtualFilesystem.Local</PackageId> <PackageId>Tesses.VirtualFilesystem.Local</PackageId>
<Author>Mike Nolan</Author> <Author>Mike Nolan</Author>
<Company>Tesses</Company> <Company>Tesses</Company>
<Version>1.0.0</Version> <Version>1.0.1</Version>
<AssemblyVersion>1.0.0</AssemblyVersion> <AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.0</FileVersion> <FileVersion>1.0.1</FileVersion>
<Description>Another VirtualFilesystem for .NET</Description> <Description>Another VirtualFilesystem for .NET</Description>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>

View File

@ -0,0 +1,510 @@
using Android.Content;
using Tesses.VirtualFilesystem;
using Android.App;
using Android.Content.PM;
using AndroidX.DocumentFile.Provider;
using Tesses.VirtualFilesystem.Extensions;
namespace Tesses.VirtualFilesystem.Filesystems;
public class SAFFileSystem : SyncFileSystem
{
public const int RequestCode = 1447775022;
public const string SharedPreferencesFile = "tesess_vfs";
public static void RequestDirectory(Activity app,bool canwrite=true,bool presistant=false,int reqCode=RequestCode)
{
Intent intent = new Intent(Intent.ActionOpenDocumentTree);
intent.AddFlags(ActivityFlags.GrantReadUriPermission|(canwrite? ActivityFlags.GrantWriteUriPermission : 0)|(presistant?ActivityFlags.GrantPersistableUriPermission:0));
app.StartActivityForResult(intent,reqCode);
}
public static SAFFileSystem? GetSAFFromResponse(Context? app,Intent? intent)
{
if(app == null || intent == null) return null;
var uri=intent.Data;
if(uri != null)
{
return new SAFFileSystem(uri,app);
}
return null;
}
public static SAFFileSystem? GetSAFFromResponsePresistant(Context? app,Intent? intent,string key)
{
if(app == null || intent == null) return null;
var uri=intent.Data;
if(uri != null)
{
var r = app?.ContentResolver;
if(r == null) return null;
var rw = intent.Flags & (ActivityFlags.GrantReadUriPermission | ActivityFlags.GrantWriteUriPermission);
r.TakePersistableUriPermission(uri,rw);
var app2=app?.CreatePackageContext(app.PackageName,0);
var sharedPrefs=app2?.GetSharedPreferences(SharedPreferencesFile,FileCreationMode.Private);
sharedPrefs?.Edit()?.PutString(key,uri.ToString())?.Apply();
return new SAFFileSystem(uri,app);
}
return null;
}
public static void Revoke(Context? app,string key)
{
var app2=app?.CreatePackageContext(app.PackageName,0);
var sharedPrefs=app2?.GetSharedPreferences(SharedPreferencesFile,FileCreationMode.Private);
var res=sharedPrefs?.GetString(key,null);
var r = app?.ContentResolver;
if(r == null) return;
if(string.IsNullOrWhiteSpace(res)) return;
var uri = Android.Net.Uri.Parse(res);
if(uri == null) return;
r.ReleasePersistableUriPermission(uri,ActivityFlags.GrantReadUriPermission);
sharedPrefs?.Edit()?.Remove(key)?.Apply();
}
public static SAFFileSystem? GetSAFFromSharedStorage(Context? app,string key)
{
if(app == null) return null;
var app2=app?.CreatePackageContext(app.PackageName,0);
var sharedPrefs=app2?.GetSharedPreferences(SharedPreferencesFile,FileCreationMode.Private);
var res=sharedPrefs?.GetString(key,null);
if(string.IsNullOrWhiteSpace(res)) return null;
var uri = Android.Net.Uri.Parse(res);
if(uri == null) return null;
return new SAFFileSystem(uri,app);
}
public SAFFileSystem(global::Android.Net.Uri uri,Context? ctx)
{
Uri = uri;
Context = ctx;
}
public global::Android.Net.Uri Uri {get;set;}
public Context? Context {get;set;}
public override void CreateDirectory(UnixPath directory)
{
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) return;
foreach(var item in directory.Parts)
{
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(!dir2.IsDirectory && !dir2.IsFile && !dir2.IsVirtual)
{
dir = dir2.CreateDirectory(item);
}
else if(dir2.IsDirectory)
{
dir = dir2;
}
}
}
}
}
public override void DeleteDirectory(UnixPath path)
{
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) return;
for(int i = 0;i<path.Parts.Length;i++)
{
var item = path.Parts[i];
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(!dir2.IsDirectory && !dir2.IsFile && !dir2.IsVirtual)
{
return;
}
else if(dir2.IsDirectory)
{
dir = dir2;
if(i==path.Parts.Length -1)
dir.Delete();
}
}
}
}
}
public override void DeleteFile(UnixPath path)
{
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) return;
for(int i = 0;i<path.Parts.Length;i++)
{
var item = path.Parts[i];
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(!dir2.IsDirectory && !dir2.IsFile && !dir2.IsVirtual)
{
return;
}
else if(dir2.IsDirectory)
{
dir=dir2;
}
else if(dir2.IsFile)
{
dir = dir2;
if(i==path.Parts.Length -1)
dir.Delete();
}
}
}
}
}
public override bool DirectoryExists(UnixPath path)
{
if(path.IsRoot) return true;
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) return false;
for(int i = 0;i<path.Parts.Length;i++)
{
var item = path.Parts[i];
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(!dir2.IsDirectory && !dir2.IsFile && !dir2.IsVirtual)
{
return false;
}
else if(dir2.IsDirectory)
{
dir = dir2;
if(i==path.Parts.Length -1)
return true;
}
}
}
}
return false;
}
public override IEnumerable<UnixPath> EnumerateFileSystemEntries(UnixPath path)
{
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) yield break;
if(path.IsRoot)
{
foreach(var _item in dir.ListFiles())
{
yield return path / _item.Name;
}
yield break;
}
for(int i = 0;i<path.Parts.Length;i++)
{
var item = path.Parts[i];
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(!dir2.IsDirectory && !dir2.IsFile && !dir2.IsVirtual)
{
yield break;
}
else if(dir2.IsDirectory)
{
dir = dir2;
if(i==path.Parts.Length -1)
{
foreach(var _item in dir2.ListFiles())
{
yield return path / _item.Name;
}
}
}
}
}
}
}
public override bool FileExists(UnixPath path)
{
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) return false;
for(int i = 0;i<path.Parts.Length;i++)
{
var item = path.Parts[i];
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(!dir2.IsDirectory && !dir2.IsFile && !dir2.IsVirtual)
{
return false;
}
else if(dir2.IsDirectory)
{
dir=dir2;
}
else if(dir2.IsFile)
{
dir = dir2;
if(i==path.Parts.Length -1)
return true;
}
}
}
}
return false;
}
public override DateTime GetCreationTime(UnixPath path)
{
return DateTime.Now;
}
public override DateTime GetLastAccessTime(UnixPath path)
{
return DateTime.Now;
}
public override DateTime GetLastWriteTime(UnixPath path)
{
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) return DateTime.Now;
for(int i = 0;i<path.Parts.Length;i++)
{
var item = path.Parts[i];
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(dir2.Exists())
{
dir = dir2;
if(i==path.Parts.Length -1)
return DateTimeOffset.FromUnixTimeSeconds(dir2.LastModified()).DateTime;
}
else return DateTime.Now;
}
}
}
return DateTime.Now;
}
public override void MoveDirectory(UnixPath src, UnixPath dest)
{
foreach(var item in this.EnumerateDirectories(src))
{
MoveDirectory(item,dest / item.Name);
}
foreach(var item in this.EnumerateFiles(src))
{
MoveFile(item,dest / item.Name);
}
DeleteDirectory(src);
}
public override void MoveFile(UnixPath src, UnixPath dest)
{
if(src.Parent == dest.Parent)
{
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) return;
for(int i = 0;i<src.Parts.Length;i++)
{
var item = src.Parts[i];
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(!dir2.IsDirectory && !dir2.IsFile && !dir2.IsVirtual)
{
return;
}
else if(dir2.IsDirectory)
{
dir=dir2;
}
else if(dir2.IsFile)
{
dir = dir2;
if(i==src.Parts.Length -1)
dir.RenameTo(dest.Name);
}
}
}
}
}
else
{
this.CopyFile(src,dest);
DeleteFile(src);
}
}
public override Stream Open(UnixPath path, FileMode mode, FileAccess access, FileShare share)
{
if(Context != null && Uri != null)
{
var dir = DocumentFile.FromTreeUri(Context,Uri);
if(dir == null) throw new FileNotFoundException(path.ToString());
for(int i = 0;i<path.Parts.Length;i++)
{
var item = path.Parts[i];
var dir2= dir?.FindFile(item);
if(dir2 != null)
{
if(!dir2.IsDirectory && !dir2.IsFile && !dir2.IsVirtual)
{
throw new FileNotFoundException(path.ToString());
}
else if(dir2.IsDirectory)
{
dir = dir2;
}
if(i==path.Parts.Length -1)
{
if(dir2?.IsFile ?? false)
{
if(mode == FileMode.CreateNew) throw new IOException("File already exists");
return _OpenFile(dir2.Uri,mode,access,share);
}
else
{
var (mimeType,displayName)=GetDisplayNameAndMime(item);
var f=dir?.CreateFile(mimeType,displayName);
if(mode == FileMode.Open || mode == FileMode.Truncate || mode == FileMode.Append)
throw new IOException("File does not exist");
if(f != null)
return _OpenFile(f.Uri,mode,access,share);
throw new IOException("Could not open file");
}
}
}
}
}
throw new FileNotFoundException(path.ToString());
}
private (string mimeType, string displayName) GetDisplayNameAndMime(string item)
{
var mime=HeyRed.Mime.MimeTypesMap.GetMimeType(item);
if(mime == "application/octet-stream")
{
return ("unknown/unknown",item);
}
else
{
return (mime,Path.GetFileNameWithoutExtension(item));
}
}
private Stream _OpenFile(global::Android.Net.Uri uri, FileMode mode, FileAccess access, FileShare share)
{
//FileMode.Append -> "a"
//FileMode.Create -> "w"
//FileMode.CreateNew -> "w"
//FileMode.Open -> "r or w"
//FileMode.OpenOrCreate -> "r or w"
//FileMode.Truncate -> "t"
//"r", "w", "wt", "wa", "rw" or "rwt"
Stream? strm = null;
if(access == FileAccess.Read)
strm = Context?.ContentResolver?.OpenInputStream(uri) ?? null;
if(access == FileAccess.Write)
if(mode == FileMode.Truncate)
strm = Context?.ContentResolver?.OpenOutputStream(uri,"wt");
else if(mode == FileMode.Append)
strm = Context?.ContentResolver?.OpenOutputStream(uri,"wa");
else
strm = Context?.ContentResolver?.OpenOutputStream(uri,"w");
if(access == FileAccess.ReadWrite)
if(mode == FileMode.Truncate)
strm = Context?.ContentResolver?.OpenOutputStream(uri,"rwt");
else
strm = Context?.ContentResolver?.OpenOutputStream(uri,"rw");
if(strm == null) throw new IOException("Failed to open stream");
return strm;
}
public override void SetCreationTime(UnixPath path, DateTime time)
{
}
public override void SetLastAccessTime(UnixPath path, DateTime time)
{
}
public override void SetLastWriteTime(UnixPath path, DateTime time)
{
}
}

View File

@ -0,0 +1,99 @@
# Tesses.VirtualFilesystem.Saf
A library to use Tesses.VirtualFilesystem with Storage Access Framework (Android)
# Nuget
# License
![GPL3-only](https://www.gnu.org/graphics/gplv3-with-text-136x68.png)
Licensed under [GPL3](https://www.gnu.org/licenses/)
# How to use (not presistant)
```csharp
...
using Tesses.VirtualFilesystem;
using Tesses.VirtualFilesystem.Filesystems;
using Android.Content;
using AndroidX.DocumentFile.Provider;
using Tesses.VirtualFilesystem.Extensions;
...
void SomeActivityFunction()
{
SAFFileSystem.RequestDirectory(this,true);
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent? data)
{
if(requestCode == SAFFileSystem.RequestCode && resultCode == Result.Ok)
{
var res=SAFFileSystem.GetSAFFromResponse(this,data);
if(res != null)
HandleFilesystem(res);
}
}
void HandleFilesystem(SAFFileSystem fs)
{
fs.CreateDirectory(Special.Root/"My Special Folder");
//read https://gitea.site.tesses.net/tesses50/tesses-vfs to get up to speed
//root is relative to the folder the user chose
//so get subdir filesystem is not needed
}
```
# How to use (presistant)
```csharp
...
using Tesses.VirtualFilesystem;
using Tesses.VirtualFilesystem.Filesystems;
using Android.Content;
using AndroidX.DocumentFile.Provider;
using Tesses.VirtualFilesystem.Extensions;
...
const string MySAFKey = "MySafKey";
void SomethingWithContext()
{
var res= SAFFileSystem.GetSAFFromSharedStorage(this,MySAFKey);
if(res != null)
HandleFilesystem(res);
}
void SomeActivityFunction()
{
var res= SAFFileSystem.GetSAFFromSharedStorage(this,MySAFKey);
if(res != null)
HandleFilesystem(res);
else
SAFFileSystem.RequestDirectory(this,true,true);
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent? data)
{
if(requestCode == SAFFileSystem.RequestCode && resultCode == Result.Ok)
{
var res=SAFFileSystem.GetSAFFromResponsePresistant(this,data,MySAFKey);
if(res != null)
HandleFilesystem(res);
}
}
void HandleFilesystem(SAFFileSystem fs)
{
fs.CreateDirectory(Special.Root/"My Special Folder");
//read https://gitea.site.tesses.net/tesses50/tesses-vfs to get up to speed
//root is relative to the folder the user chose
//so get subdir filesystem is not needed
}
```
# How to revoke permissions
```csharp
...
using Tesses.VirtualFilesystem;
using Tesses.VirtualFilesystem.Filesystems;
using Android.Content;
using AndroidX.DocumentFile.Provider;
using Tesses.VirtualFilesystem.Extensions;
...
const string MySAFKey = "MySafKey";
void SomethingWithContext()
{
SAFFileSystem.Revoke(this,MySAFKey);
}
```

View File

@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0-android</TargetFramework>
<SupportedOSPlatformVersion>21</SupportedOSPlatformVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<IsPackable>true</IsPackable>
<PackageId>Tesses.VirtualFilesystem.Saf</PackageId>
<Author>Mike Nolan</Author>
<Company>Tesses</Company>
<Version>1.0.0</Version>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Description>Tesses.VirtualFilesystem for Android Storage Access Framework (Is slow though)</Description>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageTags>VFS, Filesystem, VirtualFilesystem</PackageTags>
<RepositoryUrl>https://gitlab.tesses.net/tesses50/tesses-vfs</RepositoryUrl>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="MimeTypesMap" Version="1.0.8" />
<PackageReference Include="Tesses.VirtualFilesystem" Version="1.0.2" />
<PackageReference Include="Xamarin.AndroidX.DocumentFile" Version="1.0.1.27" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
</Project>

View File

@ -21,13 +21,20 @@ using Tesses.VirtualFilesystem;
using System.Collections.Generic; using System.Collections.Generic;
using System; using System;
using System.IO; using System.IO;
using Tesses.VirtualFilesystem.Extensions;
namespace Tesses.VirtualFilesystem.Filesystems namespace Tesses.VirtualFilesystem.Filesystems
{ {
public class ZioFileSystem : SyncFileSystem public class ZioFileSystem : SyncFileSystem
{ {
IFileSystem fs; IFileSystem fs;
public IFileSystem Filesystem => fs;
public override void Dispose()
{
fs.Dispose();
}
public ZioFileSystem(IFileSystem fs) public ZioFileSystem(IFileSystem fs)
{ {
this.fs = fs; this.fs = fs;
@ -171,7 +178,301 @@ namespace Tesses.VirtualFilesystem.Filesystems
public event EventHandler<VirtualWatcherChangedArgs> Deleted; public event EventHandler<VirtualWatcherChangedArgs> Deleted;
public event EventHandler<VirtualWatcherRenamedEventArgs> Renamed; public event EventHandler<VirtualWatcherRenamedEventArgs> Renamed;
public event EventHandler<ErrorEventArgs> Error; public event EventHandler<ErrorEventArgs> Error;
public void Dispose()
{
this.watcher.Dispose();
}
} }
} }
public class ZioMountableWrapper : ZioFileSystem
{
public MountFileSystem MountFilesystem {get;}
private ZioMountableWrapper(MountFileSystem fs) : base(fs)
{
MountFilesystem = fs;
}
public static ZioMountableWrapper Create(IVirtualFilesystem fs,bool owned=true)
{
return new ZioMountableWrapper(new MountFileSystem(new TessesVFSFilesystem(fs),owned));
}
public static ZioMountableWrapper Create(bool owned=true)
{
return new ZioMountableWrapper(new MountFileSystem(owned));
}
public void Mount(UnixPath path,IVirtualFilesystem fs)
{
MountFilesystem.Mount(path.Path,new TessesVFSFilesystem(fs));
}
public bool IsMounted(UnixPath path)
{
return MountFilesystem.IsMounted(path.Path);
}
public Dictionary<UnixPath,IVirtualFilesystem> GetMounts()
{
Dictionary<UnixPath,IVirtualFilesystem> fs=new Dictionary<UnixPath, IVirtualFilesystem>();
foreach(var item in MountFilesystem.GetMounts())
{
var _fs=item.Value as TessesVFSFilesystem;
if(_fs !=null)
fs.Add(item.Key.FullName,_fs.Filesystem);
}
return fs;
}
}
} }
namespace Zio.FileSystems
{
public class TessesVFSFilesystem : Zio.FileSystems.FileSystem
{
protected override void Dispose(bool disposing)
{
if(disposing)
fs.Dispose();
}
IVirtualFilesystem fs;
public IVirtualFilesystem Filesystem => fs;
public TessesVFSFilesystem(IVirtualFilesystem fs)
{
this.fs = fs;
}
protected override UPath ConvertPathFromInternalImpl(string innerPath)
{
return innerPath;
}
protected override string ConvertPathToInternalImpl(UPath path)
{
return path.FullName;
}
protected override void CopyFileImpl(UPath srcPath, UPath destPath, bool overwrite)
{
if(!overwrite && fs.FileExists(fs.ConvertPathToUnixPath(ConvertPathToInternal(destPath)))) throw new IOException("File already exists");
fs.CopyFile(fs.ConvertPathToUnixPath(ConvertPathToInternal(srcPath)),fs.ConvertPathToUnixPath(ConvertPathToInternal(destPath)));
}
protected override void CreateDirectoryImpl(UPath path)
{
fs.CreateDirectory(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override void DeleteDirectoryImpl(UPath path, bool isRecursive)
{
fs.DeleteDirectory(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)),isRecursive);
}
protected override void DeleteFileImpl(UPath path)
{
fs.DeleteFile(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override bool DirectoryExistsImpl(UPath path)
{
return fs.DirectoryExists(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override IEnumerable<FileSystemItem> EnumerateItemsImpl(UPath path, SearchOption searchOption, SearchPredicate searchPredicate)
{
foreach(var item in fs.EnumerateFileSystemEntries(fs.ConvertPathToUnixPath(ConvertPathToInternal(path))))
{
yield return new FileSystemItem(this,ConvertPathFromInternal(fs.ConvertPathFromUnixPath(item)),fs.DirectoryExists(item));
}
}
protected override IEnumerable<UPath> EnumeratePathsImpl(UPath path, string searchPattern, SearchOption searchOption, SearchTarget searchTarget)
{
foreach(var item in fs.EnumerateFileSystemEntries(fs.ConvertPathToUnixPath(ConvertPathToInternal(path))))
{
yield return ConvertPathFromInternal(fs.ConvertPathFromUnixPath(item));
}
}
protected override bool FileExistsImpl(UPath path)
{
return fs.FileExists(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override FileAttributes GetAttributesImpl(UPath path)
{
return fs.GetAttributes(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override DateTime GetCreationTimeImpl(UPath path)
{
return fs.GetCreationTime(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override long GetFileLengthImpl(UPath path)
{
using(var f = fs.OpenRead(fs.ConvertPathToUnixPath(ConvertPathToInternal(path))))
{
return f.Length;
}
}
protected override DateTime GetLastAccessTimeImpl(UPath path)
{
return fs.GetLastAccessTime(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override DateTime GetLastWriteTimeImpl(UPath path)
{
return fs.GetLastWriteTime(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override void MoveDirectoryImpl(UPath srcPath, UPath destPath)
{
fs.MoveDirectory(fs.ConvertPathToUnixPath(ConvertPathToInternal(srcPath)),fs.ConvertPathToUnixPath(ConvertPathToInternal(destPath)));
}
protected override void MoveFileImpl(UPath srcPath, UPath destPath)
{
fs.MoveDirectory(fs.ConvertPathToUnixPath(ConvertPathToInternal(srcPath)),fs.ConvertPathToUnixPath(ConvertPathToInternal(destPath)));
}
protected override Stream OpenFileImpl(UPath path, FileMode mode, FileAccess access, FileShare share)
{
return fs.Open(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)),mode,access,share);
}
protected override void ReplaceFileImpl(UPath srcPath, UPath destPath, UPath destBackupPath, bool ignoreMetadataErrors)
{
MoveFile(destPath,destBackupPath);
MoveFile(srcPath,destPath);
}
protected override void SetAttributesImpl(UPath path, FileAttributes attributes)
{
fs.SetAttributes(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)),attributes);
}
protected override void SetCreationTimeImpl(UPath path, DateTime time)
{
fs.SetCreationTime(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)),time);
}
protected override void SetLastAccessTimeImpl(UPath path, DateTime time)
{
fs.SetLastAccessTime(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)),time);
}
protected override void SetLastWriteTimeImpl(UPath path, DateTime time)
{
fs.SetLastWriteTime(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)),time);
}
protected override IFileSystemWatcher WatchImpl(UPath path)
{
return new _FSW(this,fs,fs.ConvertPathToUnixPath(ConvertPathToInternal(path)));
}
protected override void CreateSymbolicLinkImpl(UPath path, UPath pathToTarget)
{
fs.CreateSymlink(fs.ConvertPathToUnixPath(ConvertPathToInternal(path)),fs.ConvertPathToUnixPath(ConvertPathToInternal(pathToTarget)));
}
protected override bool TryResolveLinkTargetImpl(UPath linkPath, out UPath resolvedPath)
{
if(fs.SymlinkExists(fs.ConvertPathToUnixPath(ConvertPathToInternal(linkPath))))
{
resolvedPath = ConvertPathFromInternal(fs.ConvertPathFromUnixPath(fs.ReadLink(fs.ConvertPathToUnixPath(ConvertPathToInternal(linkPath)))));
return true;
}
else
{
resolvedPath = ConvertPathFromInternal(fs.ConvertPathFromUnixPath(Special.Root));
return false;
}
}
private class _FSW : IFileSystemWatcher
{
IVirtualWatcher watcher;
public _FSW(IFileSystem fs2,IVirtualFilesystem fs,UnixPath unixPath)
{
this.FileSystem=fs2;
watcher=fs.WatchDirectory(unixPath);
Path = fs2.ConvertPathFromInternal(fs.ConvertPathFromUnixPath(unixPath));
this._fs = fs;
watcher.Changed += W_Changed;
watcher.Created += W_Created;
watcher.Deleted += W_Deleted;
watcher.Error += W_Error;
watcher.Renamed += W_Renamed;
}
private void W_Renamed(object sender, VirtualWatcherRenamedEventArgs e)
{
Renamed?.Invoke(this,new FileRenamedEventArgs(FileSystem,(Zio.WatcherChangeTypes)e.ChangeType,FileSystem.ConvertPathFromInternal(_fs.ConvertPathFromUnixPath(e.FullPath)),FileSystem.ConvertPathFromInternal(_fs.ConvertPathFromUnixPath(e.OldFullPath))));
}
private void W_Error(object sender, System.IO.ErrorEventArgs e)
{
Error?.Invoke(this,new FileSystemErrorEventArgs(e.GetException()));
}
private void W_Deleted(object sender, VirtualWatcherChangedArgs e)
{
Deleted?.Invoke(this,new FileChangedEventArgs(FileSystem,(Zio.WatcherChangeTypes)e.ChangeType,FileSystem.ConvertPathFromInternal(_fs.ConvertPathFromUnixPath(e.FullPath))));
}
IVirtualFilesystem _fs;
private void W_Created(object sender, VirtualWatcherChangedArgs e)
{
Created?.Invoke(this,new FileChangedEventArgs(FileSystem,(Zio.WatcherChangeTypes)e.ChangeType,FileSystem.ConvertPathFromInternal(_fs.ConvertPathFromUnixPath(e.FullPath))));
}
private void W_Changed(object sender, VirtualWatcherChangedArgs e)
{
Changed?.Invoke(this,new FileChangedEventArgs(FileSystem,(Zio.WatcherChangeTypes)e.ChangeType,FileSystem.ConvertPathFromInternal(_fs.ConvertPathFromUnixPath(e.FullPath))));
}
public IFileSystem FileSystem {get;}
public UPath Path {get;}
public int InternalBufferSize { get => watcher.InternalBufferSize; set => watcher.InternalBufferSize = value; }
public Zio.NotifyFilters NotifyFilter { get => (Zio.NotifyFilters)watcher.NotifyFilter; set => watcher.NotifyFilter = (System.IO.NotifyFilters)value; }
public bool EnableRaisingEvents { get => watcher.EnableRaisingEvents; set => watcher.EnableRaisingEvents=value; }
public string Filter { get => watcher.Filter; set => watcher.Filter=value; }
public bool IncludeSubdirectories { get => watcher.IncludeSubdirectories; set => watcher.IncludeSubdirectories=value; }
public event EventHandler<FileChangedEventArgs> Changed;
public event EventHandler<FileChangedEventArgs> Created;
public event EventHandler<FileChangedEventArgs> Deleted;
public event EventHandler<FileSystemErrorEventArgs> Error;
public event EventHandler<FileRenamedEventArgs> Renamed;
public void Dispose()
{
watcher.Changed -= W_Changed;
watcher.Created -= W_Created;
watcher.Deleted -= W_Deleted;
watcher.Error -= W_Error;
watcher.Renamed -= W_Renamed;
watcher.Dispose();
}
}
}
}

View File

@ -2,14 +2,16 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Tesses.VirtualFilesystem\Tesses.VirtualFilesystem.csproj" /> <ProjectReference Include="..\Tesses.VirtualFilesystem\Tesses.VirtualFilesystem.csproj" />
<ProjectReference Include="..\Tesses.VirtualFilesystem.Extensions\Tesses.VirtualFilesystem.Extensions.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Zio" Version="0.16.1" /> <PackageReference Include="Zio" Version="0.19.1" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\"/> <None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup> </ItemGroup>
<PropertyGroup> <PropertyGroup>
@ -18,9 +20,9 @@
<PackageId>Tesses.VirtualFilesystem.Zio</PackageId> <PackageId>Tesses.VirtualFilesystem.Zio</PackageId>
<Author>Mike Nolan</Author> <Author>Mike Nolan</Author>
<Company>Tesses</Company> <Company>Tesses</Company>
<Version>1.0.0</Version> <Version>1.0.1</Version>
<AssemblyVersion>1.0.0</AssemblyVersion> <AssemblyVersion>1.0.1</AssemblyVersion>
<FileVersion>1.0.0</FileVersion> <FileVersion>1.0.1</FileVersion>
<Description>Another VirtualFilesystem for .NET</Description> <Description>Another VirtualFilesystem for .NET</Description>
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression> <PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>