1 #Region "Microsoft.VisualBasic::c6795b3109517c1ff0cd60caae3da7fd, Microsoft.VisualBasic.Core\Extensions\IO\Extensions\PathExtensions.vb"
2
3     ' Author:
4     
5     '       asuka (amethyst.asuka@gcmodeller.org)
6     '       xie (genetics@smrucc.org)
7     '       xieguigang (xie.guigang@live.com)
8     
9     ' Copyright (c) 2018 GPL3 Licensed
10     
11     
12     ' GNU GENERAL PUBLIC LICENSE (GPL3)
13     
14     
15     ' This program is free software: you can redistribute it and/or modify
16     ' it under the terms of the GNU General Public License as published by
17     ' the Free Software Foundation, either version 3 of the License, or
18     ' (at your option) any later version.
19     
20     ' This program is distributed in the hope that it will be useful,
21     ' but WITHOUT ANY WARRANTY; without even the implied warranty of
22     ' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23     ' GNU General Public License for more details.
24     
25     ' You should have received a copy of the GNU General Public License
26     ' along with this program. If not, see <http://www.gnu.org/licenses/>.
27
28
29
30     ' /********************************************************************************/
31
32     ' Summaries:
33
34     Module PathExtensions
35     
36     '     Function: BaseName, ChangeSuffix, Delete, DIR, DirectoryExists
37     '               DirectoryName, EnumerateFiles, ExtensionSuffix, FileCopy, (+2 Overloads) FileExists
38     '               FileLength, FileMove, FileName, FileOpened, GetBaseName
39     '               GetDirectoryFullPath, GetFile, GetFullPath, GetMostAppreancePath, ListDirectory
40     '               ListFiles, LoadEntryList, (+3 Overloads) LoadSourceEntryList, Long2Short, (+2 Overloads) NormalizePathString
41     '               ParentDirName, ParentPath, PathCombine, PathIllegal, ReadDirectory
42     '               (+2 Overloads) RelativePath, SafeCopyTo, SourceCopy, SplitPath, TheFile
43     '               ToDIR_URL, ToFileURL, TrimDIR, TrimSuffix, UnixPath
44     
45     '     Sub: MkDIR
46     
47     ' /********************************************************************************/
48
49 #End Region
50
51 Imports System.Collections.ObjectModel
52 Imports System.IO
53 Imports System.Math
54 Imports System.Reflection
55 Imports System.Runtime.CompilerServices
56 Imports System.Text
57 Imports System.Text.RegularExpressions
58 Imports Microsoft.VisualBasic.CommandLine.Reflection
59 Imports Microsoft.VisualBasic.ComponentModel.DataSourceModel
60 Imports Microsoft.VisualBasic.FileIO
61 Imports Microsoft.VisualBasic.FileIO.Path
62 Imports Microsoft.VisualBasic.Language
63 Imports Microsoft.VisualBasic.Language.Default
64 Imports Microsoft.VisualBasic.Language.UnixBash
65 Imports Microsoft.VisualBasic.Linq.Extensions
66 Imports Microsoft.VisualBasic.Scripting.MetaData
67 Imports Microsoft.VisualBasic.Serialization.JSON
68 Imports Microsoft.VisualBasic.Text
69
70 ''' <summary>
71 ''' Search the path from a specific keyword.(通过关键词来推测路径)
72 ''' </summary>
73 ''' <remarks></remarks>
74 <Package("Program.Path.Search", Description:="A utility tools for searching a specific file of its path on the file system more easily.")>
75 Public Module PathExtensions
76
77     ''' <summary>
78     ''' 修改路径字符串之中的文件名后缀拓展名为<paramref name="newSuffix"/>指定的值。<paramref name="newSuffix"/>不需要添加小数点
79     ''' </summary>
80     ''' <param name="path$"></param>
81     ''' <param name="newSuffix$">新的文件拓展名,这个拓展名不带有小数点,例如需要修改为*.csv,则直接赋值csv即可</param>
82     ''' <returns></returns>
83     ''' 
84     <MethodImpl(MethodImplOptions.AggressiveInlining)>
85     <Extension>
86     Public Function ChangeSuffix(path$, newSuffix$) As String
87         Return path.TrimSuffix & "." & newSuffix
88     End Function
89
90     <MethodImpl(MethodImplOptions.AggressiveInlining)>
91     <Extension>
92     Public Function SplitPath(path As StringAs String()
93         Return path.Replace("/"c, "\"c) _
94                    .StringReplace("\\{2,}""\") _
95                    .Trim("\"c) _
96                    .Split("\"c)
97     End Function
98
99     ''' <summary>
100     ''' Execute file delete
101     ''' </summary>
102     ''' <param name="path$"></param>
103     ''' <param name="throwEx"></param>
104     ''' <returns></returns>
105     <Extension> Public Function Delete(path$, Optional throwEx As Boolean = FalseAs Boolean
106         Try
107             If path.FileExists Then
108                 Call FileIO.FileSystem.DeleteFile(
109                    path, UIOption.OnlyErrorDialogs, RecycleOption.DeletePermanently
110                 )
111             ElseIf path.DirectoryExists Then
112                 Call FileIO.FileSystem.DeleteDirectory(
113                     path, DeleteDirectoryOption.DeleteAllContents
114                 )
115             End If
116
117             Return True
118         Catch ex As Exception
119             If throwEx Then
120                 Throw New Exception(path, ex)
121             Else
122                 Call App.LogException(ex, path)
123             End If
124
125             Return False
126         Finally
127         End Try
128     End Function
129
130     ''' <summary>
131     ''' 函数返回文件的拓展名后缀,请注意,这里的返回值是不会带有小数点的
132     ''' </summary>
133     ''' <param name="path$"></param>
134     ''' <returns></returns>
135     <MethodImpl(MethodImplOptions.AggressiveInlining)>
136     <Extension>
137     Public Function ExtensionSuffix(path$) As String
138         If path.StringEmpty Then
139             Return ""
140         Else
141             Return path.Split("."c).Last
142         End If
143     End Function
144
145     ''' <summary>
146     ''' Combine directory path.(这个主要是用于生成文件夹名称)
147     ''' 
148     ''' ###### Example usage
149     ''' 
150     ''' ```vbnet
151     ''' Dim images As Dictionary(Of StringString) =
152     '''     (ls - l - {"*.png", "*.jpg", "*.gif"} &lt;= PlatformEngine.wwwroot.DIR("images")) _
153     '''     .ToDictionary(Function(file) file.StripAsId,
154     '''                   AddressOf FileName)
155     ''' ```
156     ''' </summary>
157     ''' <param name="d"></param>
158     ''' <param name="name"></param>
159     ''' <returns></returns>
160     <MethodImpl(MethodImplOptions.AggressiveInlining)>
161     <Extension>
162     Public Function DIR(d As DirectoryInfo, name$) As String
163         Return $"{d.FullName}/{name}"
164     End Function
165
166     <MethodImpl(MethodImplOptions.AggressiveInlining)>
167     <Extension>
168     Public Function UnixPath(path As StringAs String
169         Return FileIO.FileSystem.GetFileInfo(path).FullName.Replace("\""/")
170     End Function
171
172     ''' <summary>
173     ''' Make directory
174     ''' </summary>
175     ''' <param name="DIR"></param>
176     <Extension> Public Sub MkDIR(DIR$, Optional throwEx As Boolean = True)
177         If DIR.StringEmpty OrElse DIR = "./" OrElse DIR = ".\" Then
178             ' 2017-12-25
179             ' 当前文件夹
180             ' 因为假若能够切换到当前文件夹的话,说明当前的文件夹已经存在了
181             ' 则在这里是否应该跳过这个创建过程
182             ' 还是不跳过吧
183             DIR = App.CurrentDirectory
184         End If
185
186         Try
187             Call FileIO.FileSystem.CreateDirectory(DIR)
188         Catch ex As Exception
189             ex = New Exception("DIR value is: " & DIR, ex)
190
191             If throwEx Then
192                 Throw ex
193             Else
194                 Call App.LogException(ex)
195             End If
196         End Try
197     End Sub
198
199     <Extension>
200     Public Function PathCombine(path As String, addTag As StringAs String
201         If path.DirectoryExists Then
202             Return path.ParentPath & "/" & path.BaseName & addTag
203         Else
204             Return path.TrimSuffix & addTag
205         End If
206     End Function
207
208     ReadOnly allKinds As New DefaultValue(Of String())({"*.*"}, Function(o) TryCast(o, String()).IsNullOrEmpty)
209
210     ''' <summary>
211     ''' 使用<see cref="FileIO.FileSystem.GetFiles"/>函数枚举
212     ''' **当前的**(不是递归的搜索所有的子文件夹)文件夹之中的
213     ''' 所有的符合条件的文件路径
214     ''' </summary>
215     ''' <param name="DIR">文件夹路径</param>
216     ''' <param name="keyword">
217     ''' Default is ``*.*`` for match any kind of files.
218     ''' (文件名进行匹配的关键词)
219     ''' </param>
220     ''' <returns></returns>
221     <Extension>
222     Public Function EnumerateFiles(DIR$, ParamArray keyword$()) As IEnumerable(Of String)
223         Return FileIO.FileSystem.GetFiles(DIR, FileIO.SearchOption.SearchTopLevelOnly, keyword Or allKinds)
224     End Function
225
226     ''' <summary>
227     ''' ```
228     ''' ls - l - r - pattern &lt;= DIR
229     ''' ```
230     ''' 
231     ''' 的简化拓展函数模式
232     ''' </summary>
233     ''' <param name="DIR$"></param>
234     ''' <param name="pattern$"></param>
235     ''' <returns></returns>
236     <MethodImpl(MethodImplOptions.AggressiveInlining)>
237     <Extension>
238     Public Function ListFiles(DIR$, Optional pattern$ = "*.*"As IEnumerable(Of String)
239         Return ls - l - r - pattern <= DIR
240     End Function
241
242     ''' <summary>
243     ''' 这个函数是会直接枚举出所有的文件路径的
244     ''' </summary>
245     ''' <param name="DIR$"></param>
246     ''' <param name="[option]"></param>
247     ''' <returns></returns>
248     <Extension>
249     Public Iterator Function ReadDirectory(DIR$, Optional [option] As FileIO.SearchOption = FileIO.SearchOption.SearchTopLevelOnly) As IEnumerable(Of String)
250         Dim current As New DirectoryInfo(DIR)
251
252         For Each file In current.EnumerateFiles
253             Yield file.FullName
254         Next
255
256         If [option] = FileIO.SearchOption.SearchAllSubDirectories Then
257             For Each folder In current.EnumerateDirectories
258                 For Each path In folder.FullName.ReadDirectory([option])
259                     Yield path
260                 Next
261             Next
262         End If
263     End Function
264
265     ''' <summary>
266     ''' Yield subfolders' FullName
267     ''' </summary>
268     ''' <param name="DIR$"></param>
269     ''' <param name="[option]"></param>
270     ''' <returns></returns>
271     <Extension>
272     Public Iterator Function ListDirectory(DIR$, Optional [option] As FileIO.SearchOption = FileIO.SearchOption.SearchTopLevelOnly) As IEnumerable(Of String)
273         Dim current As New DirectoryInfo(DIR)
274
275         For Each folder In current.EnumerateDirectories
276             Yield folder.FullName
277
278             If [option] = FileIO.SearchOption.SearchAllSubDirectories Then
279                 For Each path In folder.FullName.ListDirectory([option])
280                     Yield path
281                 Next
282             End If
283         Next
284     End Function
285
286     ''' <summary>
287     ''' 这个函数只会返回文件列表之中的第一个文件,故而需要提取某一个文件夹之中的某一个特定的文件,推荐使用这个函数(这个函数默认只查找第一级文件夹,不会进行递归搜索)
288     ''' </summary>
289     ''' <param name="DIR$"></param>
290     ''' <param name="keyword$"></param>
291     ''' <param name="opt"></param>
292     ''' <returns>当查找不到目标文件或者文件夹不存在的时候会返回空值</returns>
293     <Extension>
294     Public Function TheFile(DIR$, keyword$, Optional opt As FileIO.SearchOption = FileIO.SearchOption.SearchTopLevelOnly) As String
295         If Not DIR.DirectoryExists Then
296             Return Nothing
297         End If
298         Return FileIO.FileSystem.GetFiles(DIR, opt, keyword).FirstOrDefault
299     End Function
300
301     ''' <summary>
302     ''' Gets the URL type file path.(获取URL类型的文件路径)
303     ''' </summary>
304     ''' <param name="Path"></param>
305     ''' <returns></returns>
306     ''' <remarks></remarks>
307     '''
308     <ExportAPI("Path2Url"Info:="Gets the URL type file path.")>
309     <Extension> Public Function ToFileURL(path As StringAs String
310         If String.IsNullOrEmpty(path) Then
311             Return ""
312         Else
313             path = FileIO.FileSystem.GetFileInfo(path).FullName
314             Return String.Format("file:///{0}", path.Replace("\""/"))
315         End If
316     End Function
317
318     <ExportAPI("DIR2URL"), ExtensionAttribute>
319     Public Function ToDIR_URL(DIR As StringAs String
320         If String.IsNullOrEmpty(DIR) Then
321             Return ""
322         Else
323             DIR = FileIO.FileSystem.GetDirectoryInfo(DIR).FullName
324             Return String.Format("file:///{0}", DIR.Replace("\""/"))
325         End If
326     End Function
327
328     ''' <summary>
329     ''' 枚举所有非法的路径字符
330     ''' </summary>
331     ''' <remarks></remarks>
332     Public Const ILLEGAL_PATH_CHARACTERS_ENUMERATION As String = ":*?""<>|&"
333     Public Const ILLEGAL_FILENAME_CHARACTERS As String = "\" & ILLEGAL_PATH_CHARACTERS_ENUMERATION
334
335     ''' <summary>
336     ''' 将目标字符串之中的非法的字符替换为"_"符号以成为正确的文件名字符串。当参数<paramref name="OnlyASCII"/>为真的时候,意味着所有的非字母或者数字的字符都会被替换为下划线,默认为真
337     ''' </summary>
338     ''' <param name="str"></param>
339     ''' <param name="OnlyASCII">当本参数为真的时候,仅26个字母,0-9数字和下划线_以及小数点可以被保留下来</param>
340     ''' <returns></returns>
341     ''' <remarks></remarks>
342     <ExportAPI("NormalizePathString")>
343     <MethodImpl(MethodImplOptions.AggressiveInlining)>
344     <Extension> Public Function NormalizePathString(str$, Optional OnlyASCII As Boolean = TrueAs String
345         Return NormalizePathString(str, "_"OnlyASCII)
346     End Function
347
348     <ExportAPI("NormalizePathString")>
349     <Extension> Public Function NormalizePathString(str$, normAs As StringOptional onlyASCII As Boolean = TrueAs String
350         Dim sb As New StringBuilder(str)
351         For Each ch As Char In ILLEGAL_FILENAME_CHARACTERS
352             Call sb.Replace(ch, normAs)
353         Next
354
355         If onlyASCII Then
356             For Each ch As Char In "()[]+-~!@#$%^&=;',"
357                 Call sb.Replace(ch, normAs)
358             Next
359         End If
360
361         Return sb.ToString
362     End Function
363
364     Const PathTooLongException =
365         "System.IO.PathTooLongException: The specified path, file name, or both are too long. The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters."
366
367     ''' <summary>
368     ''' 假设文件名过长发生在文件名和最后一个文件夹的路径之上
369     ''' </summary>
370     ''' <param name="path"></param>
371     ''' <returns></returns>
372     ''' <remarks>
373     ''' System.IO.PathTooLongException: The specified path, file name, or both are too long.
374     ''' The fully qualified file name must be less than 260 characters, and the directory name must be less than 248 characters.
375     ''' </remarks>
376     <Extension> Public Function Long2Short(path As String, <CallerMemberName> Optional caller As String = ""As String
377         Dim parent As String = path.ParentPath
378         Dim DIRTokens As String() = parent.Replace("\""/").Split("/"c)
379         ' 请注意,由于path参数可能是相对路径,所以在这里DIRname和name要分开讨论
380         Dim DIRname As String = DIRTokens.Last
381         ' 因为相对路径最终会出现文件夹名称,但在path里面可能是使用小数点来表示的
382         Dim name As String = path.Replace("\""/").Split("/"c).Last
383
384         If parent.Length + name.Length >= 259 Then
385             DIRname = Mid(DIRname, 1, 20) & "~"
386             Dim ext As String = name.Split("."c).Last
387             name = Mid(name, 1, 20) & "~." & ext
388             parent = String.Join("/", DIRTokens.Take(DIRTokens.Length - 1).ToArray)
389             parent &= "/" & DIRname
390             parent &= "/" & name
391
392             Dim ex As Exception = New PathTooLongException(PathTooLongException)
393             ex = New Exception(path, ex)
394             ex = New Exception("But the path was corrected as:   " & parent & "  to avoid the crashed problem.", ex)
395             Call ex.PrintException
396             Call App.LogException(ex, caller & " -> " & MethodBase.GetCurrentMethod.GetFullName)
397
398             Return parent.Replace("\""/")
399         Else
400             Return FileIO.FileSystem.GetFileInfo(path).FullName
401         End If
402     End Function
403
404     ''' <summary>
405     ''' + C:\
406     ''' + AB:\
407     ''' + AB2:\
408     ''' + etc...
409     ''' </summary>
410     Const DriveLabel$ = "[a-zA-Z]([a-zA-Z0-9])*"
411
412     ''' <summary>
413     ''' File path illegal?
414     ''' </summary>
415     ''' <param name="path"></param>
416     ''' <returns></returns>
417     <ExportAPI("Path.Illegal?")>
418     <Extension> Public Function PathIllegal(path As StringAs Boolean
419         Dim tokens$() = Strings.Split(path.Replace("\""/"), ":/")
420
421         If tokens.Length > 2 Then  ' 有多余一个的驱动器符,则肯定是非法的路径格式
422             Return False
423         ElseIf tokens.Length = 2 Then
424             ' 完整路径
425             ' 当有很多个驱动器的时候,这里会不止一个字母
426             If Not tokens(0).IsPattern(DriveLabel, RegexICSng) Then
427                 ' 开头的驱动器的符号不正确
428                 Return False
429             Else
430                 ' 驱动器的符号也正确
431             End If
432         Else
433             ' 只有一个,则是相对路径
434         End If
435
436         Dim fileName As String = tokens.Last
437
438         ' 由于这里是判断文件是否合法,所以之判断文件名就行了,即token列表的最后一个元素
439         For Each ch As Char In ILLEGAL_PATH_CHARACTERS_ENUMERATION
440             If fileName.IndexOf(ch) > -1 Then
441                 Return True
442             End If
443         Next
444
445         Return False
446     End Function
447
448     ''' <summary>
449     ''' Gets the file length, if the path is not exists, then returns -1.
450     ''' (安全的获取文件的大小,如果目标文件不存在的话会返回-1)
451     ''' </summary>
452     ''' <param name="path"></param>
453     ''' <returns></returns>
454     <Extension>
455     Public Function FileLength(path As StringAs Long
456         If Not path.FileExists Then
457             Return -1&
458         Else
459             Return FileIO.FileSystem.GetFileInfo(path).Length
460         End If
461     End Function
462
463     ''' <summary>
464     ''' Safe file copy operation.
465     ''' (请注意,<paramref name="copyTo"/>参数的字符串最末尾必须是``/``或者``\``才会被认作为目录路径)
466     ''' </summary>
467     ''' <param name="source$"></param>
468     ''' <param name="copyTo$">
469     ''' Can be file name or directory name.
470     ''' 
471     ''' + If this paramter is a file path, then you can copy the 
472     '''   source file to another location with renamed.
473     ''' + If this parameter is a directory location, then you can 
474     '''   copy the source file to another location with the 
475     '''   identical file name.
476     ''' 
477     ''' Please notice that, the directory path should end with 
478     ''' path seperator symbol: ``\`` or ``/``.
479     ''' </param>
480     ''' <returns></returns>
481     <Extension> Public Function FileCopy(source$, copyTo$) As Boolean
482         Try
483             If copyTo.Last = "/"OrElse copyTo.Last = "\"Then
484                 copyTo = copyTo & source.FileName
485             End If
486
487             If copyTo.FileExists Then
488                 Call FileIO.FileSystem.DeleteFile(copyTo)
489             Else
490                 Call copyTo.ParentPath.MkDIR
491             End If
492
493             Call FileIO.FileSystem.CopyFile(source, copyTo)
494         Catch ex As Exception
495             ex = New Exception({source, copyTo}.GetJson, ex)
496             App.LogException(ex)
497
498             Return False
499         End Try
500
501         Return True
502     End Function
503
504     <Extension>
505     Public Function FileMove(source$, target$) As Boolean
506         Try
507             Call My.Computer.FileSystem.MoveFile(source, target)
508             Return True
509         Catch ex As Exception
510             ex = New Exception("source: " & source, ex)
511             ex = New Exception("target: " & target, ex)
512
513             Call App.LogException(ex)
514
515             Return False
516         End Try
517     End Function
518
519     ''' <summary>
520     ''' Check if the target file object is exists on your file system or not.
521     ''' (这个函数也会自动检查目标<paramref name="path"/>参数是否为空)
522     ''' </summary>
523     ''' <param name="path"></param>
524     ''' <param name="ZERO_Nonexists">将0长度的文件也作为不存在</param>
525     ''' <returns></returns>
526     ''' <remarks></remarks>
527 #If FRAMEWORD_CORE Then
528     <ExportAPI("File.Exists"Info:="Check if the target file object is exists on your file system or not.")>
529     <Extension> Public Function FileExists(path$, Optional ZERO_Nonexists As Boolean = FalseAs Boolean
530 #Else
531     <Extension> Public Function FileExists(path As StringAs Boolean
532 #End If
533
534         If path.StringEmpty Then
535             Return False
536         End If
537         If path.IndexOf(ASCII.CR) > -1 OrElse path.IndexOf(ASCII.LF) > -1 Then
538             Return False ' 包含有回车符或者换行符,则肯定不是文件路径了
539         End If
540
541         If Not String.IsNullOrEmpty(path) AndAlso
542             FileIO.FileSystem.FileExists(path) Then  ' 文件存在
543
544             If ZERO_Nonexists Then
545                 Return FileSystem.FileLen(path) > 0
546             Else
547                 Return True
548             End If
549         Else
550             Return False
551         End If
552     End Function
553
554     ''' <summary>
555     ''' Determine that the target directory is exists on the file system or not?(判断文件夹是否存在)
556     ''' </summary>
557     ''' <param name="DIR"></param>
558     ''' <returns></returns>
559     <ExportAPI("DIR.Exists"Info:="Determine that the target directory is exists on the file system or not?")>
560     <MethodImpl(MethodImplOptions.AggressiveInlining)>
561     <Extension>
562     Public Function DirectoryExists(DIR As StringAs Boolean
563         Return Not String.IsNullOrEmpty(DIR) AndAlso
564             FileIO.FileSystem.DirectoryExists(DIR)
565     End Function
566
567     ''' <summary>
568     ''' Get the directory its name of the target <paramref name="dir"/> directory
569     ''' </summary>
570     ''' <param name="dir$"></param>
571     ''' <returns></returns>
572     ''' 
573     <MethodImpl(MethodImplOptions.AggressiveInlining)>
574     <Extension> Public Function DirectoryName(dir$) As String
575         Return dir.TrimDIR _
576             .Split("\"c).Last _
577             .Split("/"c).Last
578     End Function
579
580     ''' <summary>
581     ''' Check if the file is opened by other code?(检测文件是否已经被其他程序打开使用之中)
582     ''' </summary>
583     ''' <param name="FileName">目标文件</param>
584     ''' <returns></returns>
585     ''' 
586     <MethodImpl(MethodImplOptions.AggressiveInlining)>
587     <ExportAPI("File.IsOpened"Info:="Detect while the target file is opened by someone process.")>
588     <Extension> Public Function FileOpened(FileName As StringAs Boolean
589         Try
590             Using FileOpenDetect As New FileStream(
591                 path:=FileName,
592                 mode:=FileMode.Open,
593                 access:=FileAccess.Read,
594                 share:=FileShare.None
595             )
596                 ' Just detects this file is occupied, no more things needs to do....
597             End Using
598
599             Return True
600         Catch ex As Exception
601             Return False
602         End Try
603     End Function
604
605     ''' <summary>
606     ''' Gets the name of the target file or directory, if the target is a file, then the name without 
607     ''' the extension suffix name.
608     ''' (获取目标文件夹的名称或者文件的不包含拓展名的名称)
609     ''' </summary>
610     ''' <returns></returns>
611     ''' <remarks>
612     ''' ###### 2017-2-14 
613     ''' 
614     ''' 原先的函数是依靠系统的API来工作的,故而只能够工作于已经存在的文件之上,
615     ''' 所以在这里为了更加方便的兼容文件夹或者文件路径,在这使用字符串的方法来
616     ''' 进行截取
617     ''' </remarks>
618     <ExportAPI(NameOf(BaseName), Info:="Gets the name of the target directory/file object.")>
619     <Extension> Public Function BaseName(fsObj$, Optional allowEmpty As Boolean = FalseAs String
620         If fsObj.StringEmpty Then
621             If allowEmpty Then
622                 Return ""
623             Else
624                 Throw New NullReferenceException(NameOf(fsObj) & " file system object handle is null!")
625             End If
626         End If
627
628         ' 前面的代码已经处理好了空字符串的情况了,在这里不会出现空字符串的错误
629         Dim t$() = fsObj.Trim("\"c, "/"c) _
630                         .Replace("\""/") _
631                         .Split("/"c) _
632                         .Last _
633                         .Split("."c)
634
635         If t.Length > 1 Then
636             ' 文件名之中并没有包含有拓展名后缀,则数组长度为1,则不跳过了
637             ' 有后缀拓展名,则split之后肯定会长度大于1的
638             t = t.Take(t.Length - 1) _
639                  .ToArray
640         End If
641
642         Dim name = String.Join(".", t)
643         Return name
644     End Function
645
646     ''' <summary>
647     ''' <see cref="basename"/> shortcuts extension.
648     ''' </summary>
649     ''' <param name="path"></param>
650     ''' <returns></returns>
651     ''' 
652     <MethodImpl(MethodImplOptions.AggressiveInlining)>
653     <Extension> Public Function GetBaseName(path As StringAs String
654         Return BaseName(path)
655     End Function
656
657     ''' <summary>
658     ''' Gets the name of the file's parent directory, returns value is a name, not path.
659     ''' (获取目标文件的父文件夹的文件夹名称,是名称而非路径)
660     ''' </summary>
661     ''' <param name="file"></param>
662     ''' <returns></returns>
663     <Extension> Public Function ParentDirName(file As StringAs String
664         Dim parentDir As String = FileIO.FileSystem.GetParentPath(file)
665         Dim parDirInfo = FileIO.FileSystem.GetDirectoryInfo(parentDir)
666         Return parDirInfo.Name
667     End Function
668
669     ''' <summary>
670     ''' Returns the directory path value of the parent directory.
671     ''' (这个函数是返回文件夹的路径而非名称,这个函数不依赖于系统的底层API,
672     ''' 因为系统的底层API对于过长的文件名会出错)
673     ''' </summary>
674     ''' <param name="file"></param>
675     ''' <returns></returns>
676     ''' <remarks>这个函数不依赖于系统的底层API,因为系统的底层API对于过长的文件名会出错</remarks>
677     <ExportAPI(NameOf(ParentPath))>
678     <Extension> Public Function ParentPath(file$, Optional full As Boolean = TrueAs String
679         file = file.Replace("\""/")
680
681         Dim parent As String = ""
682         Dim t As String() = file.Split("/"c)
683
684         If full Then
685             If InStr(file, "../") = 1 Then
686                 parent = FileIO.FileSystem.GetParentPath(App.CurrentDirectory)
687                 t = t.Skip(1).ToArray
688                 parent &= "/"
689             ElseIf InStr(file, "./") = 1 Then
690                 parent = App.CurrentDirectory
691                 t = t.Skip(1).ToArray
692                 parent &= "/"
693             Else
694
695             End If
696
697             If file.Last = "/"Then ' 是一个文件夹
698                 parent &= String.Join("/", t.Take(t.Length - 2).ToArray)
699             Else
700                 parent &= String.Join("/", t.Take(t.Length - 1).ToArray)
701             End If
702
703             If parent.StringEmpty Then
704                 ' 用户直接输入了一个文件名,没有包含文件夹部分,则默认是当前的文件夹
705                 parent = App.CurrentDirectory
706             End If
707         Else
708             parent = String.Join("/", t.Take(t.Length - 1).ToArray)
709         End If
710
711         Return parent
712     End Function
713
714     ''' <summary>
715     '''
716     ''' </summary>
717     ''' <param name="DIR"></param>
718     ''' <param name="keyword"></param>
719     ''' <param name="ext">元素的排布是有顺序的</param>
720     ''' <returns></returns>
721     ''' <remarks></remarks>
722     '''
723     <ExportAPI("Get.File.Path")>
724     <Extension> Public Function GetFile(DIR As String,
725                                        <Parameter("Using.Keyword")> keyword As String,
726                                        <Parameter("List.Ext")> ParamArray ext As String()) _
727                                     As <FunctionReturns("A list of file path which match with the keyword and the file extension name.")> String()
728
729         Dim Files As IEnumerable(Of String) = ls - l - wildcards(ext) <= DIR
730         Dim matches = (From Path As String
731                        In Files.AsParallel
732                        Let NameID = BaseName(Path)
733                        Where InStr(NameID, keyword, CompareMethod.Text) > 0
734                        Let ExtValue = Path.Split("."c).Last
735                        Select Path,
736                            ExtValue)
737         Dim LQuery =
738             From extType As String
739             In ext
740             Select From path
741                    In matches
742                    Where InStr(extType, path.ExtValue, CompareMethod.Text) > 0
743                    Select path.Path
744         Return LQuery.IteratesALL.Distinct.ToArray
745     End Function
746
747     ''' <summary>
748     ''' [<see cref="FileIO.SearchOption.SearchAllSubDirectories"/>,这个函数会扫描目标文件夹下面的所有文件。]
749     ''' 请注意,本方法是不能够产生具有相同的主文件名的数据的。假若目标GBK是使用本模块之中的方法保存或者导出来的,
750     ''' 则可以使用本方法生成Entry列表;(在返回的结果之中,KEY为文件名,没有拓展名,VALUE为文件的路径)
751     ''' </summary>
752     ''' <param name="source"></param>
753     ''' <returns></returns>
754     ''' <remarks></remarks>
755     '''
756     <ExportAPI("Load.ResourceEntry",
757                Info:="Load the file from a specific directory from the source parameter as the resource entry list.")>
758     <Extension>
759     Public Function LoadSourceEntryList(<Parameter("Dir.Source""The source directory which will be searchs for file.")> source As String,
760                                         <Parameter("List.Ext""The list of the file extension.")> ext As String(),
761                                         Optional topLevel As Boolean = TrueAs Dictionary(Of StringString)
762
763         If ext.IsNullOrEmpty Then
764             ext = {"*.*"}
765         End If
766
767         Dim LQuery = (From path As String
768                       In If(topLevel, ls - l, ls - l - r) - wildcards(ext) <= source
769                       Select ID = BaseName(path),
770                           path
771                       Group By ID Into Group).ToArray
772
773         ext = LinqAPI.Exec(Of String) <= From value As String
774                                          In ext
775                                          Select value.Split(CChar(".")).Last.ToLower
776
777         Dim res As Dictionary(Of StringString) = LQuery _
778             .ToDictionary(Function(x) x.ID,
779                           Function(x)
780
781                               Return LinqAPI.DefaultFirst(Of String) _
782  _
783                                 () <= From path
784                                       In x.Group
785                                       Let pathValue = path.path
786                                       Let extValue As String = pathValue.Split("."c).Last.ToLower
787                                       Where Array.IndexOf(ext, extValue) > -1
788                                       Select pathValue
789
790                           End Function)
791
792         With From entry
793              In res
794              Where Not String.IsNullOrEmpty(entry.Value)
795              Select entry
796
797             res = .ToDictionary(Function(x) x.Key,
798                                 Function(x) x.Value)
799         End With
800
801         Call $"{NameOf(ProgramPathSearchTool)} load {res.Count} source entry...".__DEBUG_ECHO
802
803         Return res
804     End Function
805
806     ''' <summary>
807     ''' 可以使用本方法生成Entry列表;(在返回的结果之中,KEY为文件名,没有拓展名,VALUE为文件的路径)
808     ''' 请注意,这个函数会搜索目标文件夹下面的所有的文件夹的
809     ''' </summary>
810     ''' <param name="source"></param>
811     ''' <param name="ext">文件类型的拓展名称</param>
812     ''' <returns></returns>
813     ''' <remarks></remarks>
814     <Extension> Public Function LoadSourceEntryList(source$, ParamArray ext$()) As Dictionary(Of StringString)
815         If Not FileIO.FileSystem.DirectoryExists(source) Then
816             Return New Dictionary(Of StringString)
817         End If
818
819         Dim LQuery = From path As String
820                      In FileIO.FileSystem.GetFiles(source, FileIO.SearchOption.SearchAllSubDirectories, ext)
821                      Select ID = BaseName(path),
822                           path
823                      Group By ID Into Group
824         Dim dict As Dictionary(Of StringString) =
825             LQuery.ToDictionary(Function(x) x.ID,
826                                 Function(x) x.Group.First.path)
827         Return dict
828     End Function
829
830     ''' <summary>
831     ''' 允许有重复的数据
832     ''' </summary>
833     ''' <param name="DIR"></param>
834     ''' <param name="exts"></param>
835     ''' <returns></returns>
836     ''' <remarks></remarks>
837     <MethodImpl(MethodImplOptions.AggressiveInlining)>
838     <ExportAPI("Load.ResourceEntry")>
839     <Extension> Public Function LoadEntryList(<Parameter("Dir.Source")> DIR$, ParamArray exts$()) As NamedValue(Of String)()
840         Return LinqAPI.Exec(Of NamedValue(Of String)) _
841  _
842             () <= From path As String
843                   In ls - l - ShellSyntax.r - wildcards(exts) <= DIR
844                   Select New NamedValue(Of StringWith {
845                       .Name = path.BaseName,
846                       .Value = path
847                   }
848
849     End Function
850
851     <ExportAPI("Load.ResourceEntry"Info:="Load the file from a specific directory from the source parameter as the resource entry list.")>
852     <Extension>
853     Public Function LoadSourceEntryList(source As IEnumerable(Of String)) As Dictionary(Of StringString)
854         Dim LQuery = From path As String
855                      In source
856                      Select ID = BaseName(path),
857                          path
858                      Group By ID Into Group
859         Dim res As Dictionary(Of StringString) =
860             LQuery.ToDictionary(Function(x) x.ID,
861                                 Function(x) x.Group.First.path)
862         Return res
863     End Function
864
865     ''' <summary>
866     ''' 将不同来源<paramref name="source"></paramref>的文件复制到目标文件夹<paramref name="copyto"></paramref>之中
867     ''' </summary>
868     ''' <param name="source"></param>
869     ''' <param name="copyto"></param>
870     ''' <returns>返回失败的文件列表</returns>
871     ''' <remarks></remarks>
872     <ExportAPI("Source.Copy",
873                Info:="Copy the file in the source list into the copyto directory, function returns the failed operation list.")>
874     Public Function SourceCopy(source As IEnumerable(Of String), CopyTo As StringOptional [Overrides] As Boolean = FalseAs String()
875         Dim failedList As New List(Of String)
876
877         For Each file As String In source
878             Try
879                 Call FileIO.FileSystem.CopyFile(file, CopyTo & "/" & FileIO.FileSystem.GetFileInfo(file).Name, [Overrides])
880             Catch ex As Exception
881                 Call failedList.Add(file)
882                 Call App.LogException(New Exception(file, ex))
883             End Try
884         Next
885
886         Return failedList.ToArray
887     End Function
888
889     <ExportAPI("Get.FrequentPath",
890                Info:="Gets a directory path which is most frequent appeared in the file list.")>
891     Public Function GetMostAppreancePath(files As IEnumerable(Of String)) As String
892         If files Is Nothing Then
893             Return ""
894         End If
895
896         Dim LQuery = From strPath As String
897                      In files
898                      Select FileIO.FileSystem.GetParentPath(strPath)
899         Return LQuery _
900             .TokenCount(ignoreCase:=True) _
901             .OrderByDescending(Function(x) x.Value) _
902             .FirstOrDefault _
903             .Key
904     End Function
905
906     ''' <summary>
907     ''' 获取相对于本应用程序的目标文件的相对路径(请注意,所生成的相对路径之中的字符串最后是没有文件夹的分隔符\或者/的)
908     ''' </summary>
909     ''' <param name="path"></param>
910     ''' <returns></returns>
911     <MethodImpl(MethodImplOptions.AggressiveInlining)>
912     <ExportAPI(NameOf(RelativePath),
913                Info:="Get the specific file system object its relative path to the application base directory.")>
914     Public Function RelativePath(path As StringAs String
915         Return RelativePath(App.HOME, path)
916     End Function
917
918     ''' <summary>
919     ''' Gets the relative path of file system object <paramref name="pcTo"/> reference to the directory path <paramref name="pcFrom"/>.
920     ''' (请注意,所生成的相对路径之中的字符串最后是没有文件夹的分隔符\或者/的)
921     ''' </summary>
922     ''' <param name="pcFrom">生成相对路径的参考文件夹</param>
923     ''' <param name="pcTo">所需要生成相对路径的目标文件系统对象的绝对路径或者相对路径</param>
924     ''' <param name="appendParent">是否将父目录的路径也添加进入相对路径之中?默认是</param>
925     ''' <returns></returns>
926     <ExportAPI(NameOf(RelativePath),
927                Info:="Gets the relative path value of pcTo file system object relative to a reference directory pcFrom")>
928     Public Function RelativePath(pcFrom$, pcTo$,
929                                  Optional appendParent As Boolean = True,
930                                  Optional fixZipPath As Boolean = FalseAs <FunctionReturns("The relative path string of pcTo file object reference to directory pcFrom")> String
931
932         Dim lcRelativePath As String = Nothing
933         Dim lcFrom As String = (If(pcFrom Is Nothing"", pcFrom.Trim().Replace("\""/")))
934         Dim lcTo As String = (If(pcTo Is Nothing"", pcTo.Trim().Replace("\""/")))
935
936         If lcFrom.Length = 0 OrElse lcTo.Length = 0 Then
937             Throw New InvalidDataException("One of the path string value is null!")
938         End If
939         If Not IO.Path.GetPathRoot(lcFrom.ToUpper()) _
940             .Equals(IO.Path.GetPathRoot(lcTo.ToUpper())) Then
941             Return pcTo
942         End If
943
944         ' 两个路径都有值并且都在相同的驱动器下才会进行计算
945
946         Dim laDirSep As Char() = {"\"c}
947         Dim lcPathFrom As String = (If(IO.Path.GetDirectoryName(lcFrom) Is Nothing, IO.Path.GetPathRoot(lcFrom.ToUpper()), IO.Path.GetDirectoryName(lcFrom)))
948         Dim lcPathTo As String = (If(IO.Path.GetDirectoryName(lcTo) Is Nothing, IO.Path.GetPathRoot(lcTo.ToUpper()), IO.Path.GetDirectoryName(lcTo)))
949         Dim lcFileTo As String = (If(IO.Path.GetFileName(lcTo) Is Nothing"", IO.Path.GetFileName(lcTo)))
950         Dim laFrom As String() = lcPathFrom.Split(laDirSep)
951         Dim laTo As String() = lcPathTo.Split(laDirSep)
952         Dim lnFromCnt As Integer = laFrom.Length
953         Dim lnToCnt As Integer = laTo.Length
954         Dim lnSame As Integer = 0
955         Dim lnCount As Integer = 0
956
957         While lnToCnt > 0 AndAlso lnSame < lnToCnt
958             If lnCount < lnFromCnt Then
959                 If laFrom(lnCount).ToUpper().Equals(laTo(lnCount).ToUpper()) Then
960                     lnSame += 1
961                 Else
962                     Exit While
963                 End If
964             Else
965                 Exit While
966             End If
967             lnCount += 1
968         End While
969
970         Dim lcEndPart As String = ""
971         For lnEnd As Integer = lnSame To lnToCnt - 1
972             If laTo(lnEnd).Length > 0 Then
973                 lcEndPart += laTo(lnEnd) & "\"
974             Else
975                 Exit For
976             End If
977         Next
978
979         Dim lnDiff As Integer = Abs(lnFromCnt - lnSame)
980         If lnDiff > 0 AndAlso laFrom(lnFromCnt - 1).Length > 0 Then
981             While lnDiff > 0
982                 lnDiff -= 1
983                 lcEndPart = "..\" & lcEndPart
984             End While
985         End If
986
987         lcRelativePath = lcEndPart & lcFileTo
988
989         If appendParent Then
990             Return "..\" & lcRelativePath
991         Else
992             If fixZipPath Then
993                 ' 2017-8-26
994                 ' 为Xlsx打包模块进行的修复
995                 Return lcRelativePath _
996                     .Split("\"c) _
997                     .Skip(1) _
998                     .JoinBy("\")
999             Else
1000                 Return lcRelativePath
1001             End If
1002         End If
1003     End Function
1004
1005     ''' <summary>
1006     ''' Gets the full path of the specific file.(为了兼容Linux,这个函数会自动替换路径之中的\为/符号)
1007     ''' </summary>
1008     ''' <param name="file"></param>
1009     ''' <returns></returns>
1010     <MethodImpl(MethodImplOptions.AggressiveInlining)>
1011     <ExportAPI("File.FullPath"Info:="Gets the full path of the file.")>
1012     <Extension> Public Function GetFullPath(file As StringAs String
1013         Return FileIO.FileSystem.GetFileInfo(file).FullName.Replace("\""/")
1014     End Function
1015
1016     ''' <summary>
1017     ''' Gets the full path of the specific directory. 
1018     ''' (这个函数为了兼容linux的文件系统,也会自动的将所有的``\``替换为``/``)
1019     ''' </summary>
1020     ''' <param name="dir"></param>
1021     ''' <param name="stack">当程序出错误的时候记录进入日志的一个追踪目标参数,调试用</param>
1022     ''' <returns></returns>
1023     <ExportAPI("Dir.FullPath"Info:="Gets the full path of the directory.")>
1024     <Extension> Public Function GetDirectoryFullPath(dir$, <CallerMemberName> Optional stack$ = NothingAs String
1025         Try
1026             Return FileIO.FileSystem _
1027                 .GetDirectoryInfo(dir) _
1028                 .FullName _
1029                 .Replace("\""/")
1030         Catch ex As Exception
1031             stack = stack & " --> " & NameOf(GetDirectoryFullPath)
1032
1033             If dir = "/" AndAlso Not App.IsMicrosoftPlatform Then
1034                 Return "/"  ' Linux上面已经是全路径了,root
1035             Else
1036                 ex = New Exception(stack & ": " & dir, ex)
1037                 Call App.LogException(ex)
1038                 Call ex.PrintException
1039                 Return dir
1040             End If
1041         End Try
1042     End Function
1043
1044     ''' <summary>
1045     ''' Removes the file extension name from the file path.(去除掉文件的拓展名)
1046     ''' </summary>
1047     ''' <param name="file"></param>
1048     ''' <returns></returns>
1049     <ExportAPI("File.Ext.Trim")>
1050     <Extension> Public Function TrimSuffix(file As StringAs String
1051         Try
1052             Dim path$ = file.FixPath.TrimEnd("/"c, "\"c)
1053             Dim fileInfo = FileIO.FileSystem.GetFileInfo(path$)
1054             Dim Name As String = BaseName(fileInfo.FullName)
1055             Return $"{fileInfo.Directory.FullName}/{Name}"
1056         Catch ex As Exception
1057             ex = New Exception($"{NameOf(file)} --> {file}", ex)
1058             Throw ex
1059         End Try
1060     End Function
1061
1062     ''' <summary>
1063     ''' Removes the last \ and / character in a directory path string.
1064     ''' (使用这个函数修剪文件夹路径之中的最后一个分隔符,以方便生成文件名)
1065     ''' </summary>
1066     ''' <param name="DIR"></param>
1067     ''' <returns></returns>
1068     <MethodImpl(MethodImplOptions.AggressiveInlining)>
1069     <Extension>
1070     Public Function TrimDIR(DIR As StringAs String
1071         Return DIR.TrimEnd("/"c, "\"c)
1072     End Function
1073
1074     ''' <summary>
1075     ''' 返回``文件名称.拓展名``
1076     ''' </summary>
1077     ''' <param name="path"></param>
1078     ''' <returns></returns>
1079     ''' 
1080     <MethodImpl(MethodImplOptions.AggressiveInlining)>
1081     <ExportAPI("File.Name")>
1082     <Extension>
1083     Public Function FileName(path As StringAs String
1084         Try
1085             Return FileIO.FileSystem.GetFileInfo(path).Name
1086         Catch ex As Exception
1087             Throw New InvalidOperationException(path, ex)
1088         End Try
1089     End Function
1090
1091     ''' <summary>
1092     ''' 进行安全的复制,出现错误不会导致应用程序崩溃,大文件不推荐使用这个函数进行复制
1093     ''' </summary>
1094     ''' <param name="source"></param>
1095     ''' <param name="copyTo"></param>
1096     ''' <returns></returns>
1097     <ExportAPI("SafeCopyTo")>
1098     Public Function SafeCopyTo(source As String, copyTo As StringAs Boolean
1099         Try
1100             Dim buf As Byte() = IO.File.ReadAllBytes(source)
1101             Call buf.FlushStream(copyTo)
1102         Catch ex As Exception
1103             Dim pt As String = $"{source.ToFileURL} ===> {copyTo.ToFileURL}"
1104             Call App.LogException(New Exception(pt, ex))
1105             Return False
1106         End Try
1107
1108         Return True
1109     End Function
1110 End Module