2022-05-20 02:06:35 +00:00
namespace TimelapseApi ;
using Eto.Forms ;
2022-05-20 05:22:28 +00:00
using Eto.Drawing ;
using System.Collections.ObjectModel ;
using Dir = System . IO . Directory ;
using System.Reflection ;
2022-05-20 02:06:35 +00:00
2022-05-20 05:22:28 +00:00
internal class OpenDialog : Dialog < string [ ] >
{
public enum DialogType
{
Open ,
Save ,
Directory
}
private static Stream [ ] GetIcons ( )
{
Stream [ ] Strms = new Stream [ 2 ] ;
var asm = Assembly . GetExecutingAssembly ( ) ;
if ( asm ! = null )
{
var strm = asm . GetManifestResourceStream ( "TimelapseApi.Assets.directory.png" ) ;
if ( strm ! = null )
{
Strms [ 0 ] = strm ;
}
strm = asm . GetManifestResourceStream ( "TimelapseApi.Assets.file.png" ) ;
if ( strm ! = null )
{
Strms [ 1 ] = strm ;
}
}
return Strms ;
}
private Stream [ ] Icons = GetIcons ( ) ;
public class OpenFile
{
public OpenFile ( Image icon , string name , string path , bool file )
{
Icon = icon ;
Name = name ;
Path = path ;
IsFile = file ;
}
public bool IsFile { get ; set ; }
public string Path { get ; set ; }
public string Name { get ; set ; }
public Image Icon { get ; set ; }
}
Button upBtn = new Button ( ) ;
TextBox path = new TextBox ( ) ;
GridView < OpenFile > view = new GridView < OpenFile > ( ) ;
ObservableCollection < OpenFile > Files = new ObservableCollection < OpenFile > ( ) ;
Image [ ] imgs = new Image [ 2 ] ;
public List < FileFilter > Filters { get ; set ; }
DialogType Type ;
DropDown extPick ;
public OpenDialog ( TimelapseFileSystem fs , DialogType type )
{
Button cancel = new Button { Text = "Cancel" } ;
cancel . Click + = ( sender , e ) = > {
Result = new string [ 0 ] ;
this . Close ( ) ;
} ;
TextBox filename = new TextBox ( ) ;
filename . PlaceholderText = "Filename" ;
Button createDir = new Button { Text = "Create Folder" } ;
createDir . Click + = ( sender , e ) = > {
if ( string . IsNullOrWhiteSpace ( filename . Text ) )
{
MessageBox . Show ( "Please type Filename" ) ;
} else {
fs . CreateDirectory ( TimelapseFileSystem . CombinePath ( Directory , filename . Text ) ) ;
}
} ;
extPick = new DropDown ( ) ;
Type = type ;
Filters = new List < FileFilter > ( ) ;
Title = Type = = DialogType . Open ? "Open File" : ( Type = = DialogType . Save ? "Save File" : "Open Directory" ) ;
path . KeyDown + = ( sender , e ) = > {
if ( e . Key = = Keys . Enter )
{
ListDirectory ( ) ;
}
} ;
upBtn . Text = "Up" ;
upBtn . Click + = ( sender , e ) = > {
string? path = Path . GetDirectoryName ( Directory ) ;
if ( ! string . IsNullOrWhiteSpace ( path ) )
{
Directory = path ;
ListDirectory ( ) ;
}
} ;
Resizable = true ;
Width = 640 ;
Height = 480 ;
imgs [ 0 ] = new Bitmap ( Icons [ 0 ] ) ;
imgs [ 1 ] = new Bitmap ( Icons [ 1 ] ) ;
Directory = "/" ;
Result = new string [ 0 ] ;
FileSystem = fs ;
MultiSelect = false ;
view . DataStore = Files ;
view . Columns . Add ( new GridColumn { DataCell = new ImageViewCell ( "Icon" ) } ) ;
view . Columns . Add ( new GridColumn { HeaderText = "Name" , DataCell = new TextBoxCell ( "Name" ) , Editable = false } ) ;
view . SelectedItemsChanged + = ( sender , e ) = > {
List < string > filenames = new List < string > ( ) ;
foreach ( var v in view . SelectedItems )
{
filenames . Add ( v . Name ) ;
}
filename . Text = string . Join ( ',' , filenames ) ;
} ;
view . CellDoubleClick + = ( sender , e ) = > {
OpenFile f = ( OpenFile ) e . Item ;
if ( f . IsFile )
{
if ( Type = = DialogType . Save )
{
if ( MessageBox . Show ( $"Do you want to overwrite {f.Path}" , MessageBoxButtons . YesNo , MessageBoxType . Question , MessageBoxDefaultButton . No ) ! = DialogResult . Yes )
{
return ;
}
}
Result = new string [ ] { f . Path } ;
this . Close ( ) ;
} else {
Directory = f . Path ;
ListDirectory ( ) ;
}
} ;
Button openBtn = new Button { Text = Type = = DialogType . Save ? "Save" : "Open" } ;
openBtn . Click + = ( sender , e ) = > {
if ( Type = = DialogType . Directory )
{
if ( view . SelectedItems . Any ( ) )
{
Result = new string [ ] { view . SelectedItem . Path } ;
this . Close ( ) ;
return ;
}
if ( ! string . IsNullOrWhiteSpace ( filename . Text ) )
{
if ( ! FileSystem . DirectoryExists ( Directory ) )
{
return ;
}
string dir = TimelapseFileSystem . CombinePath ( Directory , filename . Text ) ;
if ( FileSystem . DirectoryExists ( dir ) )
{
Result = new string [ ] { dir } ;
this . Close ( ) ;
return ;
}
}
if ( FileSystem . DirectoryExists ( Directory ) )
{
Result = new string [ ] { Directory } ;
this . Close ( ) ;
return ;
}
}
else
{
if ( view . SelectedItems . Any ( ) )
{
List < string > paths = new List < string > ( ) ;
foreach ( var item in view . SelectedItems )
{
if ( ! item . IsFile )
{
return ;
}
if ( Type = = DialogType . Save & & FileSystem . FileExists ( item . Path ) )
{
if ( MessageBox . Show ( $"Do you want to overwrite {item.Path}" , MessageBoxButtons . YesNo , MessageBoxType . Question , MessageBoxDefaultButton . No ) ! = DialogResult . Yes )
{
return ;
}
}
paths . Add ( item . Path ) ;
}
Result = paths . ToArray ( ) ;
this . Close ( ) ;
return ;
}
if ( ! string . IsNullOrWhiteSpace ( filename . Text ) )
{
string dir = TimelapseFileSystem . CombinePath ( Directory , filename . Text ) ;
if ( ! FileSystem . DirectoryExists ( Directory ) )
{
return ;
}
if ( Type = = DialogType . Open )
{
if ( FileSystem . FileExists ( dir ) )
{
Result = new string [ ] { dir } ;
this . Close ( ) ;
return ;
}
} else {
if ( FileSystem . FileExists ( dir ) )
{
if ( MessageBox . Show ( $"Do you want to overwrite {dir}" , MessageBoxButtons . YesNo , MessageBoxType . Question , MessageBoxDefaultButton . No ) ! = DialogResult . Yes )
{
return ;
}
Result = new string [ ] { dir } ;
this . Close ( ) ;
return ;
} else {
Result = new string [ ] { dir } ;
this . Close ( ) ;
return ;
}
}
}
}
} ;
DynamicLayout lyt = new DynamicLayout ( ) ;
lyt . BeginVertical ( ) ;
lyt . BeginHorizontal ( ) ;
lyt . Add ( upBtn ) ;
lyt . Add ( path , true ) ;
lyt . EndHorizontal ( ) ;
lyt . EndBeginVertical ( ) ;
lyt . BeginHorizontal ( ) ;
lyt . Add ( createDir ) ;
lyt . Add ( filename , true ) ;
if ( Type ! = DialogType . Directory )
{
lyt . Add ( extPick ) ;
}
lyt . EndHorizontal ( ) ;
lyt . EndBeginVertical ( ) ;
lyt . BeginHorizontal ( ) ;
lyt . AddSpace ( true ) ;
lyt . Add ( cancel ) ;
lyt . Add ( openBtn ) ;
lyt . EndBeginVertical ( ) ;
lyt . BeginHorizontal ( ) ;
lyt . Add ( view , true , true ) ;
lyt . EndHorizontal ( ) ;
lyt . EndVertical ( ) ;
this . Content = lyt ;
}
private void ListDirectory ( )
{
Files . Clear ( ) ;
foreach ( var dir in FileSystem . GetDirectories ( Directory ) )
{
Files . Add ( new OpenFile ( imgs [ 0 ] , dir , TimelapseFileSystem . CombinePath ( Directory , dir ) , false ) ) ;
}
if ( Type ! = DialogType . Directory ) {
List < string > validExts = new List < string > ( ) ;
if ( Filters . Count > 0 & & extPick . SelectedIndex > - 1 )
{
foreach ( var ext in Filters [ extPick . SelectedIndex ] . Extensions )
{
validExts . Add ( ext ) ;
}
}
foreach ( var dir in FileSystem . GetFiles ( Directory ) )
{
string ext = Path . GetExtension ( dir ) ;
bool add = true ;
if ( validExts . Count > 0 )
{
add = validExts . Contains ( ext ) ;
}
if ( add )
Files . Add ( new OpenFile ( imgs [ 1 ] , dir , TimelapseFileSystem . CombinePath ( Directory , dir ) , true ) ) ;
}
}
}
protected override void OnShown ( EventArgs e )
{
foreach ( var item in Filters )
{
extPick . Items . Add ( item . Name ) ;
}
if ( extPick . Items . Count > 0 )
{
extPick . SelectedIndex = 0 ;
}
view . AllowMultipleSelection = MultiSelect ;
ListDirectory ( ) ;
extPick . SelectedIndexChanged + = ( sender , e ) = > {
ListDirectory ( ) ;
} ;
}
protected override void OnClosed ( EventArgs e )
{
foreach ( var i in imgs )
{
if ( i ! = null & & ! i . IsDisposed ) i . Dispose ( ) ;
}
foreach ( var i in Icons )
{
if ( i ! = null ) i . Dispose ( ) ;
}
}
public bool MultiSelect { get { return view . AllowMultipleSelection ; } set { view . AllowMultipleSelection = value ; } }
public string Directory { get { return path . Text ; } set { path . Text = value ; } }
public TimelapseFileSystem FileSystem { get ; set ; }
}
2022-05-20 02:06:35 +00:00
public class Protect : TimelapseFileSystem
{
TimelapseFileSystem fs ;
public Protect ( TimelapseFileSystem fs )
{
this . fs = fs ;
}
public override void CreateDirectory ( string path )
{
fs . CreateDirectory ( path ) ;
}
public override void DeleteDirectory ( string path )
{
fs . DeleteDirectory ( path ) ;
}
public override void DeleteFile ( string path )
{
fs . DeleteFile ( path ) ;
}
public override bool DirectoryExists ( string path )
{
return fs . DirectoryExists ( path ) ;
}
public override bool FileExists ( string path )
{
return fs . FileExists ( path ) ;
}
public override Stream Open ( string path , FileMode mode , FileAccess access , FileShare share )
{
return fs . Open ( path , mode , access , share ) ;
}
protected override IEnumerable < string > GetDirectoriesImpl ( string path , string filter = "*" )
{
foreach ( var items in fs . GetDirectories ( path , filter ) )
{
yield return CombinePath ( path , items ) ;
}
}
public override string? ShowSaveDialog ( Window parent , FileFilter [ ] filters , string startDir = "" )
{
return fs . ShowSaveDialog ( parent , filters , startDir ) ;
}
public override string [ ] ShowOpenDialog ( Window parent , FileFilter [ ] filters , bool multi , string startDir = "" )
{
return fs . ShowOpenDialog ( parent , filters , multi , startDir ) ;
}
public override string? ShowDirectoryDialog ( Window parent , string startDir = "" )
{
return fs . ShowDirectoryDialog ( parent , startDir ) ;
}
protected override IEnumerable < string > GetFilesImpl ( string path , string filter = "*" )
{
foreach ( var items in fs . GetFiles ( path , filter ) )
{
yield return CombinePath ( path , items ) ;
}
}
}
public class NativeFileSystem : TimelapseFileSystem
{
2022-05-20 05:22:28 +00:00
internal Api ? api ;
2022-05-20 02:06:35 +00:00
public override void CreateDirectory ( string path )
{
Directory . CreateDirectory ( path ) ;
}
public override void DeleteDirectory ( string path )
{
Directory . Delete ( path ) ;
}
public override void DeleteFile ( string path )
{
File . Delete ( path ) ;
}
public override bool DirectoryExists ( string path )
{
return Directory . Exists ( path ) ;
}
public override bool FileExists ( string path )
{
return File . Exists ( path ) ;
}
public override Stream Open ( string path , FileMode mode , FileAccess access , FileShare share )
{
return File . Open ( path , mode , access , share ) ;
}
protected override IEnumerable < string > GetDirectoriesImpl ( string path , string filter = "*" )
{
return Directory . EnumerateDirectories ( path , filter ) ;
}
public override string? ShowDirectoryDialog ( Window parent , string startDir = "" )
{
2022-05-20 05:22:28 +00:00
if ( api ! = null & & api . Model . useCustomFilePickerForNativeFS ) {
return base . ShowDirectoryDialog ( parent , startDir ) ;
}
2022-05-20 02:06:35 +00:00
using ( var d = new SelectFolderDialog ( ) )
{
if ( ! string . IsNullOrWhiteSpace ( startDir ) )
{
d . Directory = startDir ;
}
return d . ShowDialog ( parent ) = = DialogResult . Ok ? d . Directory : null ;
}
}
2022-05-20 05:22:28 +00:00
public override string [ ] ShowOpenDialog ( Window parent , FileFilter [ ] filters , bool multi , string startDir = "" )
2022-05-20 02:06:35 +00:00
{
2022-05-20 05:22:28 +00:00
if ( api ! = null & & api . Model . useCustomFilePickerForNativeFS ) {
return base . ShowOpenDialog ( parent , filters , multi , startDir ) ;
}
2022-05-20 02:06:35 +00:00
using ( var ofd = new OpenFileDialog ( ) )
{
ofd . MultiSelect = multi ;
if ( ! string . IsNullOrWhiteSpace ( startDir ) )
{
ofd . Directory = new Uri ( startDir ) ;
}
foreach ( var f in filters )
{
ofd . Filters . Add ( f ) ;
}
return ofd . ShowDialog ( parent ) = = DialogResult . Ok ? ofd . Filenames . ToArray ( ) : new string [ 0 ] ;
}
}
public override string? ShowSaveDialog ( Window parent , FileFilter [ ] filters , string startDir = "" )
{
2022-05-20 05:22:28 +00:00
if ( api ! = null & & api . Model . useCustomFilePickerForNativeFS ) {
return base . ShowSaveDialog ( parent , filters , startDir ) ;
}
2022-05-20 02:06:35 +00:00
using ( var sfd = new SaveFileDialog ( ) )
{
if ( ! string . IsNullOrWhiteSpace ( startDir ) )
{
sfd . Directory = new Uri ( startDir ) ;
}
foreach ( var f in filters )
{
sfd . Filters . Add ( f ) ;
}
return sfd . ShowDialog ( parent ) = = DialogResult . Ok ? sfd . FileName : null ;
}
}
public override void DeleteDirectory ( string path , bool recursive )
{
Directory . Delete ( path , recursive ) ;
}
protected override IEnumerable < string > GetFilesImpl ( string path , string filter = "*" )
{
return Directory . EnumerateFiles ( path , filter ) ;
}
}
public abstract class TimelapseFileSystem
{
public string ReadAllText ( string path )
{
using ( var s = Open ( path , FileMode . Open , FileAccess . Read , FileShare . Read ) )
{
using ( var sr = new StreamReader ( s ) )
{
return sr . ReadToEnd ( ) ;
}
}
}
public void WriteAllText ( string path , string? contents )
{
using ( var s = Open ( path , FileMode . Create , FileAccess . Write , FileShare . None ) )
{
using ( var sw = new StreamWriter ( s ) )
{
sw . Write ( contents ) ;
}
}
}
public string? ShowOpenDialog ( Window parent , FileFilter [ ] filters , string startDir = "" )
{
var res = ShowOpenDialog ( parent , filters , false , startDir ) ;
return res . Length = = 1 ? res [ 0 ] : null ;
}
public virtual string [ ] ShowOpenDialog ( Window parent , FileFilter [ ] filters , bool multi , string startDir = "" )
{
2022-05-20 05:22:28 +00:00
using ( var ofd = new OpenDialog ( this , OpenDialog . DialogType . Open ) )
{
ofd . MultiSelect = multi ;
foreach ( var f in filters )
{
ofd . Filters . Add ( f ) ;
}
if ( ! string . IsNullOrWhiteSpace ( startDir ) )
{
ofd . Directory = startDir ;
}
return ofd . ShowModal ( parent ) ;
}
2022-05-20 02:06:35 +00:00
}
public virtual string? ShowSaveDialog ( Window parent , FileFilter [ ] filters , string startDir = "" )
{
2022-05-20 05:22:28 +00:00
using ( var ofd = new OpenDialog ( this , OpenDialog . DialogType . Save ) )
{
foreach ( var f in filters )
{
ofd . Filters . Add ( f ) ;
}
if ( ! string . IsNullOrWhiteSpace ( startDir ) )
{
ofd . Directory = startDir ;
}
var res = ofd . ShowModal ( parent ) ;
return res . Length = = 1 ? res [ 0 ] : null ;
}
2022-05-20 02:06:35 +00:00
}
public virtual string? ShowDirectoryDialog ( Window parent , string startDir = "" )
{
2022-05-20 05:22:28 +00:00
using ( var ofd = new OpenDialog ( this , OpenDialog . DialogType . Directory ) )
{
if ( ! string . IsNullOrWhiteSpace ( startDir ) )
{
ofd . Directory = startDir ;
}
var res = ofd . ShowModal ( parent ) ;
return res . Length = = 1 ? res [ 0 ] : null ;
}
2022-05-20 02:06:35 +00:00
}
public int GetNumberOfFiles ( string path , string filter = "*" )
{
return GetFilesImpl ( path , filter ) . Count ( ) ;
}
public int GetNumberOfDirectories ( string path , string filter = "*" )
{
return GetDirectoriesImpl ( path , filter ) . Count ( ) ;
}
public IEnumerable < string > GetFiles ( string path , string filter = "*" )
{
foreach ( var f in GetFilesImpl ( path , filter ) )
{
yield return Path . GetFileName ( f ) ;
}
}
public IEnumerable < string > GetDirectories ( string path , string filter = "*" )
{
foreach ( var f in GetDirectoriesImpl ( path , filter ) )
{
yield return Path . GetFileName ( f ) ;
}
}
public static string CombinePath ( params string [ ] path )
{
if ( path . Length = = 0 ) return "/" ;
string path0 = path [ 0 ] ;
foreach ( var p in path . Skip ( 1 ) )
{
path0 = CombinePath ( path0 , p ) ;
}
return path0 ;
}
public abstract void DeleteFile ( string path ) ;
public abstract void DeleteDirectory ( string path ) ;
public virtual void DeleteDirectory ( string path , bool recursive )
{
if ( recursive )
{
foreach ( var d in GetDirectories ( path ) )
{
DeleteDirectory ( CombinePath ( path , d ) , recursive ) ;
}
foreach ( var f in GetFiles ( path ) )
{
DeleteFile ( CombinePath ( path , f ) ) ;
}
}
DeleteDirectory ( path ) ;
}
public abstract void CreateDirectory ( string path ) ;
public abstract bool FileExists ( string path ) ;
public abstract bool DirectoryExists ( string path ) ;
public static string CombinePath ( string p1 , string p2 )
{
2022-05-20 05:22:28 +00:00
return Path . Combine ( p1 , p2 . TrimStart ( '/' ) ) . Replace ( Path . DirectorySeparatorChar , '/' ) ;
2022-05-20 02:06:35 +00:00
}
public abstract Stream Open ( string path , FileMode mode , FileAccess access , FileShare share ) ;
protected abstract IEnumerable < string > GetFilesImpl ( string path , string filter = "*" ) ;
protected abstract IEnumerable < string > GetDirectoriesImpl ( string path , string filter = "*" ) ;
public static SubFileSystem operator + ( TimelapseFileSystem fs , string name )
{
SubFileSystem _fs = new SubFileSystem ( fs , name ) ;
return _fs ;
}
}
public class SubFileSystem : TimelapseFileSystem
{
public SubFileSystem ( TimelapseFileSystem fs , string name )
{
var _fs = fs as SubFileSystem ;
if ( _fs ! = null )
{
FileSystem = _fs . FileSystem ;
CurrentDirectory = CombinePath ( _fs . CurrentDirectory , name ) ;
} else {
FileSystem = fs ;
CurrentDirectory = CombinePath ( "/" , name ) ;
}
}
public TimelapseFileSystem FileSystem { get ; set ; }
public string CurrentDirectory { get ; set ; }
public override void CreateDirectory ( string path )
{
FileSystem . CreateDirectory ( CombinePath ( CurrentDirectory , path ) ) ;
}
public override void DeleteDirectory ( string path )
{
FileSystem . DeleteDirectory ( CombinePath ( CurrentDirectory , path ) ) ;
}
public override void DeleteFile ( string path )
{
FileSystem . DeleteFile ( CombinePath ( CurrentDirectory , path ) ) ;
}
public override bool DirectoryExists ( string path )
{
return FileSystem . DirectoryExists ( CombinePath ( CurrentDirectory , path ) ) ;
}
public override bool FileExists ( string path )
{
return FileSystem . FileExists ( CombinePath ( CurrentDirectory , path ) ) ;
}
public override Stream Open ( string path , FileMode mode , FileAccess access , FileShare share )
{
return FileSystem . Open ( CombinePath ( CurrentDirectory , path ) , mode , access , share ) ;
}
protected override IEnumerable < string > GetDirectoriesImpl ( string path , string filter = "*" )
{
foreach ( var file in FileSystem . GetDirectories ( CombinePath ( CurrentDirectory , path ) , filter ) )
{
yield return Path . GetFileName ( file ) ;
}
}
public override string [ ] ShowOpenDialog ( Window parent , FileFilter [ ] filters , bool multi , string startDir = "" )
{
string startDir1 ;
if ( string . IsNullOrWhiteSpace ( startDir ) )
{
startDir1 = CurrentDirectory ;
} else {
startDir1 = CombinePath ( CurrentDirectory , startDir ) ;
}
var res = FileSystem . ShowOpenDialog ( parent , filters , multi , startDir1 ) ;
List < string > validUrls = new List < string > ( ) ;
foreach ( var url in res )
{
if ( url . StartsWith ( CurrentDirectory ) )
{
validUrls . Add ( url ) ;
} else
{
MessageBox . Show ( $"You can't open file \" { url } \ " because it exists outside of \"{CurrentDirectory}\"" , MessageBoxButtons . OK , MessageBoxType . Error ) ;
}
}
return validUrls . ToArray ( ) ;
}
public override string? ShowSaveDialog ( Window parent , FileFilter [ ] filters , string startDir = "" )
{
string startDir1 ;
if ( string . IsNullOrWhiteSpace ( startDir ) )
{
startDir1 = CurrentDirectory ;
} else {
startDir1 = CombinePath ( CurrentDirectory , startDir ) ;
}
var url = FileSystem . ShowSaveDialog ( parent , filters , startDir1 ) ;
if ( ! string . IsNullOrWhiteSpace ( url ) & & ! url . StartsWith ( CurrentDirectory ) )
{
MessageBox . Show ( $"You can't save file \" { url } \ " because it exists outside of \"{CurrentDirectory}\"" , MessageBoxButtons . OK , MessageBoxType . Error ) ;
return null ;
}
return url ;
}
public override string? ShowDirectoryDialog ( Window parent , string startDir = "" )
{
string startDir1 ;
if ( string . IsNullOrWhiteSpace ( startDir ) )
{
startDir1 = CurrentDirectory ;
} else {
startDir1 = CombinePath ( CurrentDirectory , startDir ) ;
}
var url = FileSystem . ShowDirectoryDialog ( parent , startDir1 ) ;
if ( ! string . IsNullOrWhiteSpace ( url ) & & ! url . StartsWith ( CurrentDirectory ) )
{
MessageBox . Show ( $"You can't open directory \" { url } \ " because it exists outside of \"{CurrentDirectory}\"" , MessageBoxButtons . OK , MessageBoxType . Error ) ;
return null ;
}
return url ;
}
protected override IEnumerable < string > GetFilesImpl ( string path , string filter = "*" )
{
foreach ( var file in FileSystem . GetFiles ( CombinePath ( CurrentDirectory , path ) , filter ) )
{
yield return Path . GetFileName ( file ) ;
}
}
public static SubFileSystem operator - ( SubFileSystem fs , int num )
{
string path = fs . CurrentDirectory ;
for ( int i = 0 ; i < num ; i + + )
{
string? p = Path . GetDirectoryName ( path ) ;
if ( string . IsNullOrWhiteSpace ( p ) )
{
path = "/" ;
break ;
} else {
path = p ;
}
}
return new SubFileSystem ( fs . FileSystem , path ) ;
}
}