fileOpr.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756
  1. package filesystem
  2. /*
  3. File Operation Wrapper
  4. author: tobychui
  5. This is a module seperated from the aroz online file system script
  6. that allows cleaner code in the main logic handler of the aroz online system.
  7. WARNING! ALL FILE OPERATION USING THIS WRAPPER SHOULD PASS IN REALPATH
  8. DO NOT USE VIRTUAL PATH FOR ANY OPERATIONS WITH THIS WRAPPER
  9. */
  10. import (
  11. "archive/zip"
  12. "compress/flate"
  13. "errors"
  14. "fmt"
  15. "io"
  16. "log"
  17. "os"
  18. "path/filepath"
  19. "strconv"
  20. "strings"
  21. "time"
  22. "imuslab.com/arozos/mod/filesystem/hidden"
  23. "imuslab.com/arozos/mod/filesystem/metadata"
  24. archiver "github.com/mholt/archiver/v3"
  25. )
  26. //A basic file zipping function
  27. func ZipFile(filelist []string, outputfile string, includeTopLevelFolder bool) error {
  28. z := archiver.Zip{
  29. CompressionLevel: flate.DefaultCompression,
  30. MkdirAll: true,
  31. SelectiveCompression: true,
  32. OverwriteExisting: false,
  33. ImplicitTopLevelFolder: includeTopLevelFolder,
  34. }
  35. err := z.Archive(filelist, outputfile)
  36. return err
  37. }
  38. //A basic file unzip function
  39. func Unzip(source, destination string) error {
  40. archive, err := zip.OpenReader(source)
  41. if err != nil {
  42. return err
  43. }
  44. defer archive.Close()
  45. for _, file := range archive.Reader.File {
  46. reader, err := file.Open()
  47. if err != nil {
  48. return err
  49. }
  50. defer reader.Close()
  51. path := filepath.Join(destination, file.Name)
  52. err = os.MkdirAll(path, os.ModePerm)
  53. if err != nil {
  54. return err
  55. }
  56. if file.FileInfo().IsDir() {
  57. continue
  58. }
  59. err = os.Remove(path)
  60. if err != nil {
  61. return err
  62. }
  63. writer, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
  64. if err != nil {
  65. return err
  66. }
  67. defer writer.Close()
  68. _, err = io.Copy(writer, reader)
  69. if err != nil {
  70. return err
  71. }
  72. }
  73. return nil
  74. }
  75. //Aroz Unzip File with progress update function (current filename / current file count / total file count / progress in percentage)
  76. func ArozUnzipFileWithProgress(filelist []string, outputfile string, progressHandler func(string, int, int, float64)) error {
  77. //Gether the total number of files in all zip files
  78. totalFileCounts := 0
  79. unzippedFileCount := 0
  80. for _, srcFile := range filelist {
  81. archive, err := zip.OpenReader(srcFile)
  82. if err != nil {
  83. return err
  84. }
  85. totalFileCounts += len(archive.Reader.File)
  86. archive.Close()
  87. }
  88. //Start extracting
  89. for _, srcFile := range filelist {
  90. archive, err := zip.OpenReader(srcFile)
  91. if err != nil {
  92. return err
  93. }
  94. defer archive.Close()
  95. for _, file := range archive.Reader.File {
  96. reader, err := file.Open()
  97. if err != nil {
  98. return err
  99. }
  100. defer reader.Close()
  101. path := filepath.Join(outputfile, file.Name)
  102. err = os.MkdirAll(path, os.ModePerm)
  103. if err != nil {
  104. return err
  105. }
  106. if file.FileInfo().IsDir() {
  107. //Folder extracted
  108. //Update the progress
  109. unzippedFileCount++
  110. progressHandler(file.Name, unzippedFileCount, totalFileCounts, float64(unzippedFileCount)/float64(totalFileCounts)*100.0)
  111. continue
  112. }
  113. err = os.Remove(path)
  114. if err != nil {
  115. return err
  116. }
  117. writer, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
  118. if err != nil {
  119. return err
  120. }
  121. defer writer.Close()
  122. _, err = io.Copy(writer, reader)
  123. if err != nil {
  124. return err
  125. }
  126. //Update the progress
  127. unzippedFileCount++
  128. progressHandler(file.Name, unzippedFileCount, totalFileCounts, float64(unzippedFileCount)/float64(totalFileCounts)*100.0)
  129. }
  130. }
  131. return nil
  132. }
  133. //Aroz Zip File with progress update function (current filename / current file count / total file count / progress in percentage)
  134. func ArozZipFileWithProgress(filelist []string, outputfile string, includeTopLevelFolder bool, progressHandler func(string, int, int, float64)) error {
  135. //Get the file count from the filelist
  136. totalFileCount := 0
  137. for _, srcpath := range filelist {
  138. if IsDir(srcpath) {
  139. filepath.Walk(srcpath, func(_ string, info os.FileInfo, _ error) error {
  140. if !info.IsDir() {
  141. totalFileCount++
  142. }
  143. return nil
  144. })
  145. } else {
  146. totalFileCount++
  147. }
  148. }
  149. //Create the target zip file
  150. file, err := os.Create(outputfile)
  151. if err != nil {
  152. panic(err)
  153. }
  154. defer file.Close()
  155. writer := zip.NewWriter(file)
  156. defer writer.Close()
  157. currentFileCount := 0
  158. for _, srcpath := range filelist {
  159. if IsDir(srcpath) {
  160. //This is a directory
  161. topLevelFolderName := filepath.ToSlash(filepath.Base(filepath.Dir(srcpath)) + "/" + filepath.Base(srcpath))
  162. err = filepath.Walk(srcpath, func(path string, info os.FileInfo, err error) error {
  163. if err != nil {
  164. return err
  165. }
  166. if info.IsDir() {
  167. return nil
  168. }
  169. if insideHiddenFolder(path) == true {
  170. //This is hidden file / folder. Skip this
  171. return nil
  172. }
  173. file, err := os.Open(path)
  174. if err != nil {
  175. return err
  176. }
  177. defer file.Close()
  178. relativePath := strings.ReplaceAll(filepath.ToSlash(path), filepath.ToSlash(filepath.Clean(srcpath))+"/", "")
  179. if includeTopLevelFolder {
  180. relativePath = topLevelFolderName + "/" + relativePath
  181. } else {
  182. relativePath = filepath.Base(srcpath) + "/" + relativePath
  183. }
  184. f, err := writer.Create(relativePath)
  185. if err != nil {
  186. return err
  187. }
  188. _, err = io.Copy(f, file)
  189. if err != nil {
  190. return err
  191. }
  192. //Update the zip progress
  193. currentFileCount++
  194. progressHandler(filepath.Base(srcpath), currentFileCount, totalFileCount, (float64(currentFileCount)/float64(totalFileCount))*float64(100))
  195. return nil
  196. })
  197. if err != nil {
  198. return err
  199. }
  200. } else {
  201. //This is a file
  202. topLevelFolderName := filepath.Base(filepath.Dir(srcpath))
  203. file, err := os.Open(srcpath)
  204. if err != nil {
  205. return err
  206. }
  207. defer file.Close()
  208. relativePath := filepath.Base(srcpath)
  209. if includeTopLevelFolder {
  210. relativePath = topLevelFolderName + "/" + relativePath
  211. }
  212. f, err := writer.Create(relativePath)
  213. if err != nil {
  214. return err
  215. }
  216. _, err = io.Copy(f, file)
  217. if err != nil {
  218. return err
  219. }
  220. //Update the zip progress
  221. currentFileCount++
  222. progressHandler(filepath.Base(srcpath), currentFileCount, totalFileCount, (float64(currentFileCount)/float64(totalFileCount))*float64(100))
  223. }
  224. }
  225. return nil
  226. }
  227. //ArOZ Zip FIle, but with no progress display
  228. func ArozZipFile(filelist []string, outputfile string, includeTopLevelFolder bool) error {
  229. //Create the target zip file
  230. file, err := os.Create(outputfile)
  231. if err != nil {
  232. return err
  233. }
  234. defer file.Close()
  235. writer := zip.NewWriter(file)
  236. defer writer.Close()
  237. for _, srcpath := range filelist {
  238. if IsDir(srcpath) {
  239. //This is a directory
  240. topLevelFolderName := filepath.ToSlash(filepath.Base(filepath.Dir(srcpath)) + "/" + filepath.Base(srcpath))
  241. err = filepath.Walk(srcpath, func(path string, info os.FileInfo, err error) error {
  242. if err != nil {
  243. return err
  244. }
  245. if info.IsDir() {
  246. return nil
  247. }
  248. if insideHiddenFolder(path) == true {
  249. //This is hidden file / folder. Skip this
  250. return nil
  251. }
  252. file, err := os.Open(path)
  253. if err != nil {
  254. return err
  255. }
  256. defer file.Close()
  257. relativePath := strings.ReplaceAll(filepath.ToSlash(path), filepath.ToSlash(filepath.Clean(srcpath))+"/", "")
  258. if includeTopLevelFolder {
  259. relativePath = topLevelFolderName + "/" + relativePath
  260. } else {
  261. relativePath = filepath.Base(srcpath) + "/" + relativePath
  262. }
  263. f, err := writer.Create(relativePath)
  264. if err != nil {
  265. return err
  266. }
  267. _, err = io.Copy(f, file)
  268. if err != nil {
  269. return err
  270. }
  271. return nil
  272. })
  273. if err != nil {
  274. return err
  275. }
  276. } else {
  277. //This is a file
  278. topLevelFolderName := filepath.Base(filepath.Dir(srcpath))
  279. file, err := os.Open(srcpath)
  280. if err != nil {
  281. return err
  282. }
  283. defer file.Close()
  284. relativePath := filepath.Base(srcpath)
  285. if includeTopLevelFolder {
  286. relativePath = topLevelFolderName + "/" + relativePath
  287. }
  288. f, err := writer.Create(relativePath)
  289. if err != nil {
  290. return err
  291. }
  292. _, err = io.Copy(f, file)
  293. if err != nil {
  294. return err
  295. }
  296. }
  297. }
  298. return nil
  299. }
  300. func insideHiddenFolder(path string) bool {
  301. FileIsHidden, err := hidden.IsHidden(path, true)
  302. if err != nil {
  303. //Read error. Maybe permission issue, assuem is hidden
  304. return true
  305. }
  306. return FileIsHidden
  307. }
  308. func ViewZipFile(filepath string) ([]string, error) {
  309. z := archiver.Zip{}
  310. filelist := []string{}
  311. err := z.Walk(filepath, func(f archiver.File) error {
  312. filelist = append(filelist, f.Name())
  313. return nil
  314. })
  315. return filelist, err
  316. }
  317. func FileCopy(src string, dest string, mode string, progressUpdate func(int, string)) error {
  318. srcRealpath, _ := filepath.Abs(src)
  319. destRealpath, _ := filepath.Abs(dest)
  320. if IsDir(src) && strings.Contains(filepath.ToSlash(destRealpath)+"/", filepath.ToSlash(srcRealpath)+"/") {
  321. //Recursive operation. Reject
  322. return errors.New("Recursive copy operation.")
  323. }
  324. //Check if the copy destination file already have an identical file
  325. copiedFilename := filepath.Base(src)
  326. if fileExists(filepath.Join(dest, filepath.Base(src))) {
  327. if mode == "" {
  328. //Do not specific file exists principle
  329. return errors.New("Destination file already exists.")
  330. } else if mode == "skip" {
  331. //Skip this file
  332. return nil
  333. } else if mode == "overwrite" {
  334. //Continue with the following code
  335. //Check if the copy and paste dest are identical
  336. if filepath.ToSlash(filepath.Clean(src)) == filepath.ToSlash(filepath.Clean(filepath.Join(dest, filepath.Base(src)))) {
  337. //Source and target identical. Cannot overwrite.
  338. return errors.New("Source and destination paths are identical.")
  339. }
  340. } else if mode == "keep" {
  341. //Keep the file but saved with 'Copy' suffix
  342. newFilename := strings.TrimSuffix(filepath.Base(src), filepath.Ext(src)) + " - Copy" + filepath.Ext(src)
  343. //Check if the newFilename already exists. If yes, continue adding suffix
  344. duplicateCounter := 0
  345. for fileExists(filepath.Join(dest, newFilename)) {
  346. duplicateCounter++
  347. newFilename = strings.TrimSuffix(filepath.Base(src), filepath.Ext(src)) + " - Copy(" + strconv.Itoa(duplicateCounter) + ")" + filepath.Ext(src)
  348. if duplicateCounter > 1024 {
  349. //Maxmium loop encountered. For thread safty, terminate here
  350. return errors.New("Too many copies of identical files.")
  351. }
  352. }
  353. copiedFilename = newFilename
  354. } else {
  355. //This exists opr not supported.
  356. return errors.New("Unknown file exists rules given.")
  357. }
  358. }
  359. //Fix the lacking / at the end if true
  360. if dest[len(dest)-1:] != "/" {
  361. dest = dest + "/"
  362. }
  363. //Ready to move. Check if both folder are located in the same root devices. If not, use copy and delete method.
  364. if IsDir(src) {
  365. //Source file is directory. CopyFolder
  366. realDest := filepath.Join(dest, copiedFilename)
  367. //err := dircpy.Copy(src, realDest)
  368. err := dirCopy(src, realDest, progressUpdate)
  369. if err != nil {
  370. return err
  371. }
  372. } else {
  373. //Source is file only. Copy file.
  374. realDest := filepath.Join(dest, copiedFilename)
  375. source, err := os.Open(src)
  376. if err != nil {
  377. return err
  378. }
  379. destination, err := os.Create(realDest)
  380. if err != nil {
  381. return err
  382. }
  383. if progressUpdate != nil {
  384. //Set progress to 100, leave it to upper level abstraction to handle
  385. progressUpdate(100, filepath.Base(realDest))
  386. }
  387. _, err = io.Copy(destination, source)
  388. if err != nil {
  389. return err
  390. }
  391. source.Close()
  392. destination.Close()
  393. }
  394. return nil
  395. }
  396. func FileMove(src string, dest string, mode string, fastMove bool, progressUpdate func(int, string)) error {
  397. srcRealpath, _ := filepath.Abs(src)
  398. destRealpath, _ := filepath.Abs(dest)
  399. if IsDir(src) && strings.Contains(filepath.ToSlash(destRealpath)+"/", filepath.ToSlash(srcRealpath)+"/") {
  400. //Recursive operation. Reject
  401. return errors.New("Recursive move operation.")
  402. }
  403. if !fileExists(dest) {
  404. if fileExists(filepath.Dir(dest)) {
  405. //User pass in the whole path for the folder. Report error usecase.
  406. return errors.New("Dest location should be an existing folder instead of the full path of the moved file.")
  407. }
  408. return errors.New("Dest folder not found")
  409. }
  410. //Fix the lacking / at the end if true
  411. if dest[len(dest)-1:] != "/" {
  412. dest = dest + "/"
  413. }
  414. //Check if the target file already exists.
  415. movedFilename := filepath.Base(src)
  416. if fileExists(filepath.Join(dest, filepath.Base(src))) {
  417. //Handle cases where file already exists
  418. if mode == "" {
  419. //Do not specific file exists principle
  420. return errors.New("Destination file already exists.")
  421. } else if mode == "skip" {
  422. //Skip this file
  423. return nil
  424. } else if mode == "overwrite" {
  425. //Continue with the following code
  426. //Check if the copy and paste dest are identical
  427. if filepath.ToSlash(filepath.Clean(src)) == filepath.ToSlash(filepath.Clean(filepath.Join(dest, filepath.Base(src)))) {
  428. //Source and target identical. Cannot overwrite.
  429. return errors.New("Source and destination paths are identical.")
  430. }
  431. } else if mode == "keep" {
  432. //Keep the file but saved with 'Copy' suffix
  433. newFilename := strings.TrimSuffix(filepath.Base(src), filepath.Ext(src)) + " - Copy" + filepath.Ext(src)
  434. //Check if the newFilename already exists. If yes, continue adding suffix
  435. duplicateCounter := 0
  436. for fileExists(filepath.Join(dest, newFilename)) {
  437. duplicateCounter++
  438. newFilename = strings.TrimSuffix(filepath.Base(src), filepath.Ext(src)) + " - Copy(" + strconv.Itoa(duplicateCounter) + ")" + filepath.Ext(src)
  439. if duplicateCounter > 1024 {
  440. //Maxmium loop encountered. For thread safty, terminate here
  441. return errors.New("Too many copies of identical files.")
  442. }
  443. }
  444. movedFilename = newFilename
  445. } else {
  446. //This exists opr not supported.
  447. return errors.New("Unknown file exists rules given.")
  448. }
  449. }
  450. if fastMove {
  451. //Ready to move with the quick rename method
  452. realDest := dest + movedFilename
  453. err := os.Rename(src, realDest)
  454. if err != nil {
  455. log.Println(err)
  456. return errors.New("File Move Failed")
  457. }
  458. } else {
  459. //Ready to move. Check if both folder are located in the same root devices. If not, use copy and delete method.
  460. if IsDir(src) {
  461. //Source file is directory. CopyFolder
  462. realDest := dest + movedFilename
  463. //err := dircpy.Copy(src, realDest)
  464. err := dirCopy(src, realDest, progressUpdate)
  465. if err != nil {
  466. return err
  467. } else {
  468. //Move completed. Remove source file.
  469. os.RemoveAll(src)
  470. return nil
  471. }
  472. } else {
  473. //Source is file only. Copy file.
  474. realDest := dest + movedFilename
  475. /*
  476. Updates 20-10-2020, replaced io.Copy to BufferedLargeFileCopy
  477. Legacy code removed.
  478. */
  479. //Update the progress
  480. if progressUpdate != nil {
  481. progressUpdate(100, filepath.Base(src))
  482. }
  483. err := BufferedLargeFileCopy(src, realDest, 8192)
  484. if err != nil {
  485. log.Println("BLFC error: ", err.Error())
  486. return err
  487. }
  488. //Delete the source file after copy
  489. err = os.Remove(src)
  490. counter := 0
  491. for err != nil {
  492. //Sometime Windows need this to prevent windows caching bring problems to file remove
  493. time.Sleep(1 * time.Second)
  494. os.Remove(src)
  495. counter++
  496. log.Println("Retrying to remove file: " + src)
  497. if counter > 10 {
  498. return errors.New("Source file remove failed.")
  499. }
  500. }
  501. }
  502. }
  503. //Remove the source cache as it no longer exists in this folder
  504. metadata.RemoveCache(src)
  505. return nil
  506. }
  507. //Copy a given directory, with no progress udpate
  508. func CopyDir(src string, dest string) error {
  509. return dirCopy(src, dest, func(progress int, name string) {})
  510. }
  511. //Replacment of the legacy dirCopy plugin with filepath.Walk function. Allowing real time progress update to front end
  512. func dirCopy(src string, realDest string, progressUpdate func(int, string)) error {
  513. //Get the total file counts
  514. totalFileCounts := int64(0)
  515. filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
  516. if !info.IsDir() {
  517. //Updates 22 April 2021, chnaged from file count to file size for progress update
  518. //totalFileCounts++
  519. totalFileCounts += info.Size()
  520. }
  521. return nil
  522. })
  523. //Make the destinaton directory
  524. if !fileExists(realDest) {
  525. os.Mkdir(realDest, 0755)
  526. }
  527. //Start moving
  528. fileCounter := int64(0)
  529. err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
  530. srcAbs, _ := filepath.Abs(src)
  531. pathAbs, _ := filepath.Abs(path)
  532. var folderRootRelative string = strings.Replace(pathAbs, srcAbs, "", 1)
  533. if folderRootRelative == "" {
  534. return nil
  535. }
  536. if info.IsDir() {
  537. //Mkdir base on relative path
  538. return os.MkdirAll(filepath.Join(realDest, folderRootRelative), 0755)
  539. } else {
  540. //fileCounter++
  541. fileCounter += info.Size()
  542. //Move file base on relative path
  543. fileSrc := filepath.ToSlash(filepath.Join(filepath.Clean(src), folderRootRelative))
  544. fileDest := filepath.ToSlash(filepath.Join(filepath.Clean(realDest), folderRootRelative))
  545. //Update move progress
  546. if progressUpdate != nil {
  547. progressUpdate(int(float64(fileCounter)/float64(totalFileCounts)*100), filepath.Base(fileSrc))
  548. }
  549. //Move the file using BLFC
  550. err := BufferedLargeFileCopy(fileSrc, fileDest, 8192)
  551. if err != nil {
  552. //Ignore and continue
  553. log.Println("BLFC Error:", err.Error())
  554. return nil
  555. }
  556. /*
  557. //Move fiel using IO Copy
  558. err := BasicFileCopy(fileSrc, fileDest)
  559. if err != nil {
  560. log.Println("Basic Copy Error: ", err.Error())
  561. return nil
  562. }
  563. */
  564. }
  565. return nil
  566. })
  567. return err
  568. }
  569. func BasicFileCopy(src string, dst string) error {
  570. sourceFileStat, err := os.Stat(src)
  571. if err != nil {
  572. return err
  573. }
  574. if !sourceFileStat.Mode().IsRegular() {
  575. return fmt.Errorf("%s is not a regular file", src)
  576. }
  577. source, err := os.Open(src)
  578. if err != nil {
  579. return err
  580. }
  581. defer source.Close()
  582. destination, err := os.Create(dst)
  583. if err != nil {
  584. return err
  585. }
  586. defer destination.Close()
  587. _, err = io.Copy(destination, source)
  588. return err
  589. }
  590. //Use for copying large file using buffering method. Allowing copying large file with little RAM
  591. func BufferedLargeFileCopy(src string, dst string, BUFFERSIZE int64) error {
  592. sourceFileStat, err := os.Stat(src)
  593. if err != nil {
  594. return err
  595. }
  596. if !sourceFileStat.Mode().IsRegular() {
  597. return errors.New("Invalid file source")
  598. }
  599. source, err := os.Open(src)
  600. if err != nil {
  601. return err
  602. }
  603. destination, err := os.Create(dst)
  604. if err != nil {
  605. return err
  606. }
  607. buf := make([]byte, BUFFERSIZE)
  608. for {
  609. n, err := source.Read(buf)
  610. if err != nil && err != io.EOF {
  611. source.Close()
  612. destination.Close()
  613. return err
  614. }
  615. if n == 0 {
  616. source.Close()
  617. destination.Close()
  618. break
  619. }
  620. if _, err := destination.Write(buf[:n]); err != nil {
  621. source.Close()
  622. destination.Close()
  623. return err
  624. }
  625. }
  626. return nil
  627. }
  628. func IsDir(path string) bool {
  629. if fileExists(path) == false {
  630. return false
  631. }
  632. fi, err := os.Stat(path)
  633. if err != nil {
  634. log.Fatal(err)
  635. return false
  636. }
  637. switch mode := fi.Mode(); {
  638. case mode.IsDir():
  639. return true
  640. case mode.IsRegular():
  641. return false
  642. }
  643. return false
  644. }