fileOpr.go 18 KB

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