tesses-vfs/Tesses.VirtualFilesystem.Saf/Class1.cs

511 lines
15 KiB
C#

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)
{
}
}