1+ using CommunityToolkit . Mvvm . DependencyInjection ;
2+ using Files . App . Contexts ;
13using Files . App . Extensions ;
24using Files . App . Filesystem . StorageItems ;
35using Files . App . Helpers ;
46using Files . App . Views ;
57using Files . Shared . Extensions ;
68using System ;
79using System . Collections . Generic ;
10+ using System . Collections . Immutable ;
811using System . IO ;
912using System . Linq ;
13+ using System . Text ;
1014using System . Threading . Tasks ;
1115using Windows . Storage ;
1216using Windows . Storage . Search ;
@@ -15,12 +19,17 @@ namespace Files.App.Filesystem
1519{
1620 public static class StorageFileExtensions
1721 {
18- public static BaseStorageFile AsBaseStorageFile ( this IStorageItem item )
22+ private const int SINGLE_DOT_DIRECTORY_LENGTH = 2 ;
23+ private const int DOUBLE_DOT_DIRECTORY_LENGTH = 3 ;
24+
25+ public static readonly ImmutableHashSet < string > _ftpPaths =
26+ new HashSet < string > ( ) { "ftp:/" , "ftps:/" , "ftpes:/" } . ToImmutableHashSet ( ) ;
27+
28+ public static BaseStorageFile ? AsBaseStorageFile ( this IStorageItem item )
1929 {
2030 if ( item is null || ! item . IsOfType ( StorageItemTypes . File ) )
21- {
2231 return null ;
23- }
32+
2433 return item is StorageFile file ? ( BaseStorageFile ) file : item as BaseStorageFile ;
2534 }
2635
@@ -55,7 +64,8 @@ public static bool AreItemsInSameDrive(this IEnumerable<string> itemsPath, strin
5564 {
5665 try
5766 {
58- return itemsPath . Any ( itemPath => Path . GetPathRoot ( itemPath ) . Equals ( Path . GetPathRoot ( destinationPath ) , StringComparison . OrdinalIgnoreCase ) ) ;
67+ var destinationRoot = Path . GetPathRoot ( destinationPath ) ;
68+ return itemsPath . Any ( itemPath => Path . GetPathRoot ( itemPath ) . Equals ( destinationRoot , StringComparison . OrdinalIgnoreCase ) ) ;
5969 }
6070 catch
6171 {
@@ -71,7 +81,8 @@ public static bool AreItemsAlreadyInFolder(this IEnumerable<string> itemsPath, s
7181 {
7282 try
7383 {
74- return itemsPath . All ( itemPath => Path . GetDirectoryName ( itemPath ) . Equals ( destinationPath . TrimPath ( ) , StringComparison . OrdinalIgnoreCase ) ) ;
84+ var trimmedPath = destinationPath . TrimPath ( ) ;
85+ return itemsPath . All ( itemPath => Path . GetDirectoryName ( itemPath ) . Equals ( trimmedPath , StringComparison . OrdinalIgnoreCase ) ) ;
7586 }
7687 catch
7788 {
@@ -83,23 +94,11 @@ public static bool AreItemsAlreadyInFolder(this IEnumerable<IStorageItem> storag
8394 public static bool AreItemsAlreadyInFolder ( this IEnumerable < IStorageItemWithPath > storageItems , string destinationPath )
8495 => storageItems . Select ( x => x . Path ) . AreItemsAlreadyInFolder ( destinationPath ) ;
8596
86- public static BaseStorageFolder AsBaseStorageFolder ( this IStorageItem item )
97+ public static BaseStorageFolder ? AsBaseStorageFolder ( this IStorageItem item )
8798 {
88- if ( item is null )
89- {
90- return null ;
91- }
92- else if ( item . IsOfType ( StorageItemTypes . Folder ) )
93- {
94- if ( item is StorageFolder folder )
95- {
96- return ( BaseStorageFolder ) folder ;
97- }
98- else
99- {
100- return item as BaseStorageFolder ;
101- }
102- }
99+ if ( item is not null && item . IsOfType ( StorageItemTypes . Folder ) )
100+ return item is StorageFolder folder ? ( BaseStorageFolder ) folder : item as BaseStorageFolder ;
101+
103102 return null ;
104103 }
105104
@@ -110,9 +109,7 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)
110109 if ( value . Contains ( '/' , StringComparison . Ordinal ) )
111110 {
112111 if ( ! value . EndsWith ( '/' ) )
113- {
114112 value += "/" ;
115- }
116113 }
117114 else if ( ! value . EndsWith ( '\\ ' ) )
118115 {
@@ -133,10 +130,8 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)
133130
134131 var component = value . Substring ( lastIndex , i - lastIndex ) ;
135132 var path = value . Substring ( 0 , i + 1 ) ;
136- if ( ! new [ ] { "ftp:/" , "ftps:/" , "ftpes:/" } . Contains ( path , StringComparer . OrdinalIgnoreCase ) )
137- {
133+ if ( ! _ftpPaths . Contains ( path , StringComparer . OrdinalIgnoreCase ) )
138134 pathBoxItems . Add ( GetPathItem ( component , path ) ) ;
139- }
140135
141136 lastIndex = i + 1 ;
142137 }
@@ -145,29 +140,10 @@ public static List<PathBoxItem> GetDirectoryPathComponents(string value)
145140 return pathBoxItems ;
146141 }
147142
148- public static string GetPathWithoutEnvironmentVariable ( string path )
143+ public static string GetResolvedPath ( string path , bool isFtp )
149144 {
150- if ( path . StartsWith ( "~\\ " , StringComparison . Ordinal ) )
151- {
152- path = $ "{ CommonPaths . HomePath } { path . Remove ( 0 , 1 ) } ";
153- }
154- if ( path . Contains ( "%temp%" , StringComparison . OrdinalIgnoreCase ) )
155- {
156- path = path . Replace ( "%temp%" , CommonPaths . TempPath , StringComparison . OrdinalIgnoreCase ) ;
157- }
158- if ( path . Contains ( "%tmp%" , StringComparison . OrdinalIgnoreCase ) )
159- {
160- path = path . Replace ( "%tmp%" , CommonPaths . TempPath , StringComparison . OrdinalIgnoreCase ) ;
161- }
162- if ( path . Contains ( "%localappdata%" , StringComparison . OrdinalIgnoreCase ) )
163- {
164- path = path . Replace ( "%localappdata%" , CommonPaths . LocalAppDataPath , StringComparison . OrdinalIgnoreCase ) ;
165- }
166- if ( path . Contains ( "%homepath%" , StringComparison . OrdinalIgnoreCase ) )
167- {
168- path = path . Replace ( "%homepath%" , CommonPaths . HomePath , StringComparison . OrdinalIgnoreCase ) ;
169- }
170- return Environment . ExpandEnvironmentVariables ( path ) ;
145+ var withoutEnvirnment = GetPathWithoutEnvironmentVariable ( path ) ;
146+ return ResolvePath ( withoutEnvirnment , isFtp ) ;
171147 }
172148
173149 public async static Task < BaseStorageFile > DangerousGetFileFromPathAsync
@@ -280,9 +256,7 @@ public async static Task<IList<StorageFolderWithPath>> GetFoldersWithPathAsync
280256 ( this StorageFolderWithPath parentFolder , string nameFilter , uint maxNumberOfItems = uint . MaxValue )
281257 {
282258 if ( parentFolder is null )
283- {
284259 return null ;
285- }
286260
287261 var queryOptions = new QueryOptions
288262 {
@@ -327,5 +301,104 @@ private static PathBoxItem GetPathItem(string component, string path)
327301 } ;
328302 }
329303 }
304+
305+ private static string GetPathWithoutEnvironmentVariable ( string path )
306+ {
307+ if ( path . StartsWith ( "~\\ " , StringComparison . Ordinal ) )
308+ path = $ "{ CommonPaths . HomePath } { path . Remove ( 0 , 1 ) } ";
309+
310+ path = path . Replace ( "%temp%" , CommonPaths . TempPath , StringComparison . OrdinalIgnoreCase ) ;
311+
312+ path = path . Replace ( "%tmp%" , CommonPaths . TempPath , StringComparison . OrdinalIgnoreCase ) ;
313+
314+ path = path . Replace ( "%localappdata%" , CommonPaths . LocalAppDataPath , StringComparison . OrdinalIgnoreCase ) ;
315+
316+ path = path . Replace ( "%homepath%" , CommonPaths . HomePath , StringComparison . OrdinalIgnoreCase ) ;
317+
318+ return Environment . ExpandEnvironmentVariables ( path ) ;
319+ }
320+
321+ private static string ResolvePath ( string path , bool isFtp )
322+ {
323+ if ( path . StartsWith ( "Home" ) )
324+ return "Home" ;
325+
326+ var pathBuilder = new StringBuilder ( path ) ;
327+ var lastPathIndex = path . Length - 1 ;
328+ var separatorChar = isFtp || path . Contains ( '/' , StringComparison . Ordinal ) ? '/' : '\\ ' ;
329+ var rootIndex = isFtp ? FtpHelpers . GetRootIndex ( path ) + 1 : path . IndexOf ( $ ":{ separatorChar } ", StringComparison . Ordinal ) + 2 ;
330+
331+ for ( int i = 0 , lastIndex = 0 ; i < pathBuilder . Length ; i ++ )
332+ {
333+ if ( pathBuilder [ i ] is not '?' &&
334+ pathBuilder [ i ] != Path . DirectorySeparatorChar &&
335+ pathBuilder [ i ] != Path . AltDirectorySeparatorChar &&
336+ i != lastIndex )
337+ continue ;
338+
339+ if ( lastIndex == i )
340+ {
341+ ++ lastIndex ;
342+ continue ;
343+ }
344+
345+ var component = pathBuilder . ToString ( ) . Substring ( lastIndex , i - lastIndex ) ;
346+ if ( component is ".." )
347+ {
348+ if ( lastIndex is 0 )
349+ {
350+ SetCurrentWorkingDirectory ( pathBuilder , separatorChar , lastIndex , ref i ) ;
351+ }
352+ else if ( lastIndex == rootIndex )
353+ {
354+ pathBuilder . Remove ( lastIndex , DOUBLE_DOT_DIRECTORY_LENGTH ) ;
355+ i = lastIndex - 1 ;
356+ }
357+ else
358+ {
359+ var directoryIndex = pathBuilder . ToString ( ) . LastIndexOf (
360+ separatorChar ,
361+ lastIndex - DOUBLE_DOT_DIRECTORY_LENGTH ) ;
362+
363+ if ( directoryIndex is not - 1 )
364+ {
365+ pathBuilder . Remove ( directoryIndex , i - directoryIndex ) ;
366+ i = directoryIndex ;
367+ }
368+ }
369+
370+ lastPathIndex = pathBuilder . Length - 1 ;
371+ }
372+ else if ( component is "." )
373+ {
374+ if ( lastIndex is 0 )
375+ {
376+ SetCurrentWorkingDirectory ( pathBuilder , separatorChar , lastIndex , ref i ) ;
377+ }
378+ else
379+ {
380+ pathBuilder . Remove ( lastIndex , SINGLE_DOT_DIRECTORY_LENGTH ) ;
381+ i -= 3 ;
382+ }
383+ lastPathIndex = pathBuilder . Length - 1 ;
384+ }
385+
386+ lastIndex = i + 1 ;
387+ }
388+
389+ return pathBuilder . ToString ( ) ;
390+ }
391+
392+ private static void SetCurrentWorkingDirectory ( StringBuilder path , char separator , int substringIndex , ref int i )
393+ {
394+ var context = Ioc . Default . GetRequiredService < IContentPageContext > ( ) ;
395+ var subPath = path . ToString ( ) . Substring ( substringIndex ) ;
396+
397+ path . Clear ( ) ;
398+ path . Append ( context . ShellPage ? . FilesystemViewModel . WorkingDirectory ) ;
399+ path . Append ( separator ) ;
400+ path . Append ( subPath ) ;
401+ i = - 1 ;
402+ }
330403 }
331404}
0 commit comments