fileOpr.go 18 KB

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