fileOpr.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986
  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/tar"
  12. "archive/zip"
  13. "compress/flate"
  14. "compress/gzip"
  15. "errors"
  16. "fmt"
  17. "io"
  18. "log"
  19. "os"
  20. "path/filepath"
  21. "strconv"
  22. "strings"
  23. "time"
  24. "imuslab.com/arozos/mod/filesystem/arozfs"
  25. "imuslab.com/arozos/mod/filesystem/hidden"
  26. archiver "github.com/mholt/archiver/v3"
  27. )
  28. // A basic file zipping function
  29. func ZipFile(filelist []string, outputfile string, includeTopLevelFolder bool) error {
  30. z := archiver.Zip{
  31. CompressionLevel: flate.DefaultCompression,
  32. MkdirAll: true,
  33. SelectiveCompression: true,
  34. OverwriteExisting: false,
  35. ImplicitTopLevelFolder: includeTopLevelFolder,
  36. }
  37. err := z.Archive(filelist, outputfile)
  38. return err
  39. }
  40. // A basic file unzip function
  41. func Unzip(source, destination string) error {
  42. archive, err := zip.OpenReader(source)
  43. if err != nil {
  44. return err
  45. }
  46. defer archive.Close()
  47. for _, file := range archive.Reader.File {
  48. reader, err := file.Open()
  49. if err != nil {
  50. return err
  51. }
  52. defer reader.Close()
  53. path := filepath.Join(destination, file.Name)
  54. err = os.MkdirAll(path, os.ModePerm)
  55. if err != nil {
  56. return err
  57. }
  58. if file.FileInfo().IsDir() {
  59. continue
  60. }
  61. err = os.Remove(path)
  62. if err != nil {
  63. return err
  64. }
  65. writer, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
  66. if err != nil {
  67. return err
  68. }
  69. defer writer.Close()
  70. _, err = io.Copy(writer, reader)
  71. if err != nil {
  72. return err
  73. }
  74. }
  75. return nil
  76. }
  77. // Aroz Unzip File with progress update function (current filename / current file count / total file count / progress in percentage)
  78. func ArozUnzipFileWithProgress(filelist []string, outputfile string, progressHandler func(string, int, int, float64) int) error {
  79. //Gether the total number of files in all zip files
  80. totalFileCounts := 0
  81. unzippedFileCount := 0
  82. for _, srcFile := range filelist {
  83. archive, err := zip.OpenReader(srcFile)
  84. if err != nil {
  85. return err
  86. }
  87. totalFileCounts += len(archive.Reader.File)
  88. archive.Close()
  89. }
  90. //Start extracting
  91. for _, srcFile := range filelist {
  92. archive, err := zip.OpenReader(srcFile)
  93. if err != nil {
  94. return err
  95. }
  96. defer archive.Close()
  97. for _, file := range archive.Reader.File {
  98. reader, err := file.Open()
  99. if err != nil {
  100. return err
  101. }
  102. defer reader.Close()
  103. path := filepath.Join(outputfile, file.Name)
  104. parentFolder := path
  105. if !file.FileInfo().IsDir() {
  106. //Is file. Change target to its parent dir
  107. parentFolder = filepath.Dir(path)
  108. }
  109. err = os.MkdirAll(parentFolder, 0775)
  110. if err != nil {
  111. return err
  112. }
  113. if file.FileInfo().IsDir() {
  114. //Folder is created already be the steps above.
  115. //Update the progress
  116. unzippedFileCount++
  117. statusCode := progressHandler(file.Name, unzippedFileCount, totalFileCounts, float64(unzippedFileCount)/float64(totalFileCounts)*100.0)
  118. for statusCode == 1 {
  119. //Wait for the task to be resumed
  120. time.Sleep(1 * time.Second)
  121. statusCode = progressHandler(file.Name, unzippedFileCount, totalFileCounts, float64(unzippedFileCount)/float64(totalFileCounts)*100.0)
  122. }
  123. if statusCode == 2 {
  124. //Cancel
  125. return errors.New("Operation cancelled by user")
  126. }
  127. continue
  128. }
  129. //Extrat and write to the target file
  130. writer, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
  131. if err != nil {
  132. return err
  133. }
  134. _, err = io.Copy(writer, reader)
  135. if err != nil {
  136. //Extraction failed. Remove this file if exists
  137. writer.Close()
  138. if FileExists(path) {
  139. os.Remove(path)
  140. }
  141. return err
  142. }
  143. writer.Close()
  144. //Update the progress
  145. unzippedFileCount++
  146. statusCode := progressHandler(file.Name, unzippedFileCount, totalFileCounts, float64(unzippedFileCount)/float64(totalFileCounts)*100.0)
  147. for statusCode == 1 {
  148. //Wait for the task to be resumed
  149. time.Sleep(1 * time.Second)
  150. statusCode = progressHandler(file.Name, unzippedFileCount, totalFileCounts, float64(unzippedFileCount)/float64(totalFileCounts)*100.0)
  151. }
  152. if statusCode == 2 {
  153. //Cancel
  154. return errors.New("Operation cancelled by user")
  155. }
  156. }
  157. }
  158. return nil
  159. }
  160. /*
  161. Aroz Zip File with progress update function
  162. Returns the following progress: (current filename / current file count / total file count / progress in percentage)
  163. if output is local path that is out of the scope of any fsh, leave outputFsh as nil
  164. */
  165. func ArozZipFileWithProgress(targetFshs []*FileSystemHandler, filelist []string, outputFsh *FileSystemHandler, outputfile string, includeTopLevelFolder bool, progressHandler func(string, int, int, float64) int) error {
  166. //fmt.Println("WEBSOCKET ZIPPING", targetFshs, filelist)
  167. //Get the file count from the filelist
  168. totalFileCount := 0
  169. for i, srcpath := range filelist {
  170. thisFsh := targetFshs[i]
  171. fshAbs := thisFsh.FileSystemAbstraction
  172. if thisFsh.FileSystemAbstraction.IsDir(srcpath) {
  173. fshAbs.Walk(srcpath, func(_ string, info os.FileInfo, _ error) error {
  174. if !info.IsDir() {
  175. totalFileCount++
  176. }
  177. return nil
  178. })
  179. } else {
  180. totalFileCount++
  181. }
  182. }
  183. //Create the target zip file
  184. var file arozfs.File
  185. var err error
  186. if outputFsh != nil {
  187. file, err = outputFsh.FileSystemAbstraction.Create(outputfile)
  188. } else {
  189. //Force local fs
  190. file, err = os.Create(outputfile)
  191. }
  192. if err != nil {
  193. return err
  194. }
  195. defer file.Close()
  196. writer := zip.NewWriter(file)
  197. defer writer.Close()
  198. currentFileCount := 0
  199. for i, srcpath := range filelist {
  200. thisFsh := targetFshs[i]
  201. fshAbs := thisFsh.FileSystemAbstraction
  202. //Local File System
  203. if fshAbs.IsDir(srcpath) {
  204. //This is a directory
  205. topLevelFolderName := filepath.ToSlash(arozfs.Base(filepath.Dir(srcpath)) + "/" + arozfs.Base(srcpath))
  206. err = fshAbs.Walk(srcpath, func(path string, info os.FileInfo, err error) error {
  207. if err != nil {
  208. return err
  209. }
  210. if info.IsDir() {
  211. return nil
  212. }
  213. if insideHiddenFolder(path) {
  214. //This is hidden file / folder. Skip this
  215. return nil
  216. }
  217. thisFile, err := fshAbs.ReadStream(path)
  218. if err != nil {
  219. return err
  220. }
  221. defer thisFile.Close()
  222. relativePath := strings.ReplaceAll(filepath.ToSlash(path), filepath.ToSlash(filepath.Clean(srcpath))+"/", "")
  223. if includeTopLevelFolder {
  224. relativePath = topLevelFolderName + "/" + relativePath
  225. } else {
  226. relativePath = arozfs.Base(srcpath) + "/" + relativePath
  227. }
  228. f, err := writer.Create(relativePath)
  229. if err != nil {
  230. return err
  231. }
  232. _, err = io.Copy(f, thisFile)
  233. if err != nil {
  234. return err
  235. }
  236. //Update the zip progress
  237. currentFileCount++
  238. statusCode := progressHandler(arozfs.Base(srcpath), currentFileCount, totalFileCount, (float64(currentFileCount)/float64(totalFileCount))*float64(100))
  239. for statusCode == 1 {
  240. //Wait for the task to be resumed
  241. time.Sleep(1 * time.Second)
  242. statusCode = progressHandler(arozfs.Base(srcpath), currentFileCount, totalFileCount, (float64(currentFileCount)/float64(totalFileCount))*float64(100))
  243. }
  244. if statusCode == 2 {
  245. //Cancel
  246. return errors.New("Operation cancelled by user")
  247. }
  248. return nil
  249. })
  250. if err != nil {
  251. return err
  252. }
  253. } else {
  254. //This is a file
  255. topLevelFolderName := arozfs.Base(filepath.Dir(srcpath))
  256. thisFile, err := fshAbs.ReadStream(srcpath)
  257. if err != nil {
  258. return err
  259. }
  260. defer thisFile.Close()
  261. relativePath := arozfs.Base(srcpath)
  262. if includeTopLevelFolder {
  263. relativePath = topLevelFolderName + "/" + relativePath
  264. }
  265. f, err := writer.Create(relativePath)
  266. if err != nil {
  267. return err
  268. }
  269. _, err = io.Copy(f, thisFile)
  270. if err != nil {
  271. return err
  272. }
  273. //Update the zip progress
  274. currentFileCount++
  275. statusCode := progressHandler(arozfs.Base(srcpath), currentFileCount, totalFileCount, (float64(currentFileCount)/float64(totalFileCount))*float64(100))
  276. for statusCode == 1 {
  277. //Wait for the task to be resumed
  278. time.Sleep(1 * time.Second)
  279. statusCode = progressHandler(arozfs.Base(srcpath), currentFileCount, totalFileCount, (float64(currentFileCount)/float64(totalFileCount))*float64(100))
  280. }
  281. if statusCode == 2 {
  282. //Cancel
  283. return errors.New("Operation cancelled by user")
  284. }
  285. }
  286. }
  287. return nil
  288. }
  289. /*
  290. ArozZipFile
  291. Zip file without progress update, support local file system or buffer space
  292. To use it with local file system, pass in nil in fsh for each item in filelist, e.g.
  293. filesystem.ArozZipFile([]*filesystem.FileSystemHandler{nil}, []string{zippingSource}, nil, targetZipFilename, false)
  294. */
  295. func ArozZipFile(sourceFshs []*FileSystemHandler, filelist []string, outputFsh *FileSystemHandler, outputfile string, includeTopLevelFolder bool) error {
  296. //Create the target zip file
  297. var file arozfs.File
  298. var err error
  299. if outputFsh != nil {
  300. file, err = outputFsh.FileSystemAbstraction.Create(outputfile)
  301. } else {
  302. //Force local fs
  303. file, err = os.Create(outputfile)
  304. }
  305. if err != nil {
  306. return err
  307. }
  308. defer file.Close()
  309. writer := zip.NewWriter(file)
  310. defer writer.Close()
  311. for i, srcpath := range filelist {
  312. thisFsh := sourceFshs[i]
  313. var fshAbs FileSystemAbstraction
  314. if thisFsh == nil {
  315. //Use local fs functions
  316. if IsDir(srcpath) {
  317. //This is a directory
  318. topLevelFolderName := filepath.ToSlash(arozfs.Base(filepath.Dir(srcpath)) + "/" + arozfs.Base(srcpath))
  319. err = filepath.Walk(srcpath, func(path string, info os.FileInfo, err error) error {
  320. if err != nil {
  321. return err
  322. }
  323. if info.IsDir() {
  324. return nil
  325. }
  326. if insideHiddenFolder(path) {
  327. //This is hidden file / folder. Skip this
  328. return nil
  329. }
  330. thisFile, err := os.Open(path)
  331. if err != nil {
  332. return err
  333. }
  334. defer thisFile.Close()
  335. relativePath := strings.ReplaceAll(filepath.ToSlash(path), filepath.ToSlash(filepath.Clean(srcpath))+"/", "")
  336. if includeTopLevelFolder {
  337. relativePath = topLevelFolderName + "/" + relativePath
  338. } else {
  339. relativePath = arozfs.Base(srcpath) + "/" + relativePath
  340. }
  341. f, err := writer.Create(relativePath)
  342. if err != nil {
  343. return err
  344. }
  345. _, err = io.Copy(f, thisFile)
  346. if err != nil {
  347. return err
  348. }
  349. return nil
  350. })
  351. if err != nil {
  352. return err
  353. }
  354. } else {
  355. //This is a file
  356. topLevelFolderName := arozfs.Base(filepath.Dir(srcpath))
  357. thisFile, err := os.Open(srcpath)
  358. if err != nil {
  359. return err
  360. }
  361. defer thisFile.Close()
  362. relativePath := arozfs.Base(srcpath)
  363. if includeTopLevelFolder {
  364. relativePath = topLevelFolderName + "/" + relativePath
  365. }
  366. f, err := writer.Create(relativePath)
  367. if err != nil {
  368. return err
  369. }
  370. _, err = io.Copy(f, thisFile)
  371. if err != nil {
  372. return err
  373. }
  374. }
  375. } else {
  376. //Use file system abstraction
  377. fshAbs = thisFsh.FileSystemAbstraction
  378. if fshAbs.IsDir(srcpath) {
  379. //This is a directory
  380. topLevelFolderName := filepath.ToSlash(arozfs.Base(filepath.Dir(srcpath)) + "/" + arozfs.Base(srcpath))
  381. err = fshAbs.Walk(srcpath, func(path string, info os.FileInfo, err error) error {
  382. if err != nil {
  383. return err
  384. }
  385. if info.IsDir() {
  386. return nil
  387. }
  388. if insideHiddenFolder(path) {
  389. //This is hidden file / folder. Skip this
  390. return nil
  391. }
  392. thisFile, err := fshAbs.ReadStream(path)
  393. if err != nil {
  394. fmt.Println(err)
  395. return err
  396. }
  397. defer thisFile.Close()
  398. relativePath := strings.ReplaceAll(filepath.ToSlash(path), filepath.ToSlash(filepath.Clean(srcpath))+"/", "")
  399. if includeTopLevelFolder {
  400. relativePath = topLevelFolderName + "/" + relativePath
  401. } else {
  402. relativePath = arozfs.Base(srcpath) + "/" + relativePath
  403. }
  404. f, err := writer.Create(relativePath)
  405. if err != nil {
  406. return err
  407. }
  408. _, err = io.Copy(f, thisFile)
  409. if err != nil {
  410. return err
  411. }
  412. return nil
  413. })
  414. if err != nil {
  415. return err
  416. }
  417. } else {
  418. //This is a file
  419. topLevelFolderName := arozfs.Base(filepath.Dir(srcpath))
  420. thisFile, err := fshAbs.ReadStream(srcpath)
  421. if err != nil {
  422. return err
  423. }
  424. defer thisFile.Close()
  425. relativePath := arozfs.Base(srcpath)
  426. if includeTopLevelFolder {
  427. relativePath = topLevelFolderName + "/" + relativePath
  428. }
  429. f, err := writer.Create(relativePath)
  430. if err != nil {
  431. return err
  432. }
  433. _, err = io.Copy(f, thisFile)
  434. if err != nil {
  435. return err
  436. }
  437. }
  438. }
  439. }
  440. return nil
  441. }
  442. func insideHiddenFolder(path string) bool {
  443. FileIsHidden, err := hidden.IsHidden(path, true)
  444. if err != nil {
  445. //Read error. Maybe permission issue, assuem is hidden
  446. return true
  447. }
  448. return FileIsHidden
  449. }
  450. func ViewZipFile(filepath string) ([]string, error) {
  451. z := archiver.Zip{}
  452. filelist := []string{}
  453. err := z.Walk(filepath, func(f archiver.File) error {
  454. filelist = append(filelist, f.Name())
  455. return nil
  456. })
  457. return filelist, err
  458. }
  459. func FileCopy(srcFsh *FileSystemHandler, src string, destFsh *FileSystemHandler, dest string, mode string, progressUpdate func(int, string) int) error {
  460. srcFshAbs := srcFsh.FileSystemAbstraction
  461. destFshAbs := destFsh.FileSystemAbstraction
  462. if srcFshAbs.IsDir(src) && strings.HasPrefix(dest, src) {
  463. //Recursive operation. Reject
  464. return errors.New("Recursive copy operation.")
  465. }
  466. //Check if the copy destination file already have an identical file
  467. copiedFilename := arozfs.Base(src)
  468. if destFshAbs.FileExists(filepath.Join(dest, arozfs.Base(src))) {
  469. if mode == "" {
  470. //Do not specific file exists principle
  471. return errors.New("Destination file already exists.")
  472. } else if mode == "skip" {
  473. //Skip this file
  474. return nil
  475. } else if mode == "overwrite" {
  476. //Continue with the following code
  477. //Check if the copy and paste dest are identical
  478. if filepath.ToSlash(filepath.Clean(src)) == filepath.ToSlash(filepath.Clean(filepath.Join(dest, arozfs.Base(src)))) {
  479. //Source and target identical. Cannot overwrite.
  480. return errors.New("Source and destination paths are identical.")
  481. }
  482. } else if mode == "keep" {
  483. //Keep the file but saved with 'Copy' suffix
  484. newFilename := strings.TrimSuffix(arozfs.Base(src), filepath.Ext(src)) + " - Copy" + filepath.Ext(src)
  485. //Check if the newFilename already exists. If yes, continue adding suffix
  486. duplicateCounter := 0
  487. for destFshAbs.FileExists(filepath.Join(dest, newFilename)) {
  488. duplicateCounter++
  489. newFilename = strings.TrimSuffix(arozfs.Base(src), filepath.Ext(src)) + " - Copy(" + strconv.Itoa(duplicateCounter) + ")" + filepath.Ext(src)
  490. if duplicateCounter > 1024 {
  491. //Maxmium loop encountered. For thread safty, terminate here
  492. return errors.New("Too many copies of identical files.")
  493. }
  494. }
  495. copiedFilename = newFilename
  496. } else {
  497. //This exists opr not supported.
  498. return errors.New("Unknown file exists rules given.")
  499. }
  500. }
  501. //Ready to move. Check if both folder are located in the same root devices. If not, use copy and delete method.
  502. if srcFshAbs.IsDir(src) {
  503. //Source file is directory. CopyFolder
  504. realDest := filepath.Join(dest, copiedFilename)
  505. err := dirCopy(srcFsh, src, destFsh, realDest, progressUpdate)
  506. if err != nil {
  507. return err
  508. }
  509. } else {
  510. //Source is file only. Copy file.
  511. realDest := filepath.Join(dest, copiedFilename)
  512. f, err := srcFshAbs.ReadStream(src)
  513. if err != nil {
  514. return err
  515. }
  516. defer f.Close()
  517. err = destFshAbs.WriteStream(realDest, f, 0775)
  518. if err != nil {
  519. return err
  520. }
  521. if progressUpdate != nil {
  522. //Set progress to 100, leave it to upper level abstraction to handle
  523. statusCode := progressUpdate(100, arozfs.Base(realDest))
  524. for statusCode == 1 {
  525. //Wait for the task to be resumed
  526. time.Sleep(1 * time.Second)
  527. statusCode = progressUpdate(100, arozfs.Base(realDest))
  528. }
  529. if statusCode == 2 {
  530. //Cancel
  531. return errors.New("Operation cancelled by user")
  532. }
  533. }
  534. }
  535. return nil
  536. }
  537. func FileMove(srcFsh *FileSystemHandler, src string, destFsh *FileSystemHandler, dest string, mode string, fastMove bool, progressUpdate func(int, string) int) error {
  538. srcAbst := srcFsh.FileSystemAbstraction
  539. destAbst := destFsh.FileSystemAbstraction
  540. src = filepath.ToSlash(src)
  541. dest = filepath.ToSlash(dest)
  542. if srcAbst.IsDir(src) && strings.HasPrefix(dest, src) {
  543. //Recursive operation. Reject
  544. return errors.New("Recursive move operation.")
  545. }
  546. if !destAbst.FileExists(dest) {
  547. if destAbst.FileExists(filepath.Dir(dest)) {
  548. //User pass in the whole path for the folder. Report error usecase.
  549. return errors.New("Dest location should be an existing folder instead of the full path of the moved file.")
  550. }
  551. os.MkdirAll(dest, 0775)
  552. }
  553. //Check if the target file already exists.
  554. movedFilename := arozfs.Base(src)
  555. if destAbst.FileExists(filepath.Join(dest, arozfs.Base(src))) {
  556. //Handle cases where file already exists
  557. if mode == "" {
  558. //Do not specific file exists principle
  559. return errors.New("Destination file already exists.")
  560. } else if mode == "skip" {
  561. //Skip this file
  562. return nil
  563. } else if mode == "overwrite" {
  564. //Continue with the following code
  565. //Check if the copy and paste dest are identical
  566. if filepath.ToSlash(filepath.Clean(src)) == filepath.ToSlash(filepath.Clean(filepath.Join(dest, arozfs.Base(src)))) {
  567. //Source and target identical. Cannot overwrite.
  568. return errors.New("Source and destination paths are identical.")
  569. }
  570. } else if mode == "keep" {
  571. //Keep the file but saved with 'Copy' suffix
  572. newFilename := strings.TrimSuffix(arozfs.Base(src), filepath.Ext(src)) + " - Copy" + filepath.Ext(src)
  573. //Check if the newFilename already exists. If yes, continue adding suffix
  574. duplicateCounter := 0
  575. for destAbst.FileExists(filepath.Join(dest, newFilename)) {
  576. duplicateCounter++
  577. newFilename = strings.TrimSuffix(arozfs.Base(src), filepath.Ext(src)) + " - Copy(" + strconv.Itoa(duplicateCounter) + ")" + filepath.Ext(src)
  578. if duplicateCounter > 1024 {
  579. //Maxmium loop encountered. For thread safty, terminate here
  580. return errors.New("Too many copies of identical files.")
  581. }
  582. }
  583. movedFilename = newFilename
  584. } else {
  585. //This exists opr not supported.
  586. return errors.New("Unknown file exists rules given.")
  587. }
  588. }
  589. if fastMove {
  590. //Ready to move with the quick rename method
  591. realDest := filepath.Join(dest, movedFilename)
  592. err := os.Rename(src, realDest)
  593. if err == nil {
  594. //Fast move success
  595. return nil
  596. }
  597. //Fast move failed. Back to the original copy and move method
  598. }
  599. //Ready to move. Check if both folder are located in the same root devices. If not, use copy and delete method.
  600. if srcAbst.IsDir(src) {
  601. //Source file is directory. CopyFolder
  602. realDest := filepath.Join(dest, movedFilename)
  603. //err := dircpy.Copy(src, realDest)
  604. err := dirCopy(srcFsh, src, destFsh, realDest, progressUpdate)
  605. if err != nil {
  606. return err
  607. } else {
  608. //Move completed. Remove source file.
  609. srcAbst.RemoveAll(src)
  610. return nil
  611. }
  612. } else {
  613. //Source is file only. Copy file.
  614. realDest := filepath.Join(dest, movedFilename)
  615. /*
  616. Updates 20-10-2020, replaced io.Copy to BufferedLargeFileCopy
  617. Legacy code removed.
  618. */
  619. //Update the progress
  620. if progressUpdate != nil {
  621. statusCode := progressUpdate(100, arozfs.Base(src))
  622. for statusCode == 1 {
  623. //Wait for the task to be resumed
  624. time.Sleep(1 * time.Second)
  625. statusCode = progressUpdate(100, arozfs.Base(realDest))
  626. }
  627. if statusCode == 2 {
  628. //Cancel
  629. return errors.New("Operation cancelled by user")
  630. }
  631. }
  632. f, err := srcAbst.ReadStream(src)
  633. if err != nil {
  634. fmt.Println(err)
  635. return err
  636. }
  637. defer f.Close()
  638. err = destAbst.WriteStream(realDest, f, 0755)
  639. if err != nil {
  640. fmt.Println(err)
  641. return err
  642. }
  643. //Delete the source file after copy
  644. err = srcAbst.Remove(src)
  645. counter := 0
  646. for err != nil {
  647. //Sometime Windows need this to prevent windows caching bring problems to file remove
  648. time.Sleep(1 * time.Second)
  649. os.Remove(src)
  650. counter++
  651. log.Println("Retrying to remove file: " + src)
  652. if counter > 10 {
  653. return errors.New("Source file remove failed.")
  654. }
  655. }
  656. }
  657. return nil
  658. }
  659. // Copy a given directory, with no progress udpate
  660. func CopyDir(srcFsh *FileSystemHandler, src string, destFsh *FileSystemHandler, dest string) error {
  661. return dirCopy(srcFsh, src, destFsh, dest, func(progress int, name string) int { return 0 })
  662. }
  663. // Replacment of the legacy dirCopy plugin with filepath.Walk function. Allowing real time progress update to front end
  664. func dirCopy(srcFsh *FileSystemHandler, src string, destFsh *FileSystemHandler, realDest string, progressUpdate func(int, string) int) error {
  665. srcFshAbs := srcFsh.FileSystemAbstraction
  666. destFshAbs := destFsh.FileSystemAbstraction
  667. //Get the total file counts
  668. totalFileCounts := int64(0)
  669. srcFshAbs.Walk(src, func(path string, info os.FileInfo, err error) error {
  670. if !info.IsDir() {
  671. //Updates 22 April 2021, chnaged from file count to file size for progress update
  672. //totalFileCounts++
  673. totalFileCounts += info.Size()
  674. }
  675. return nil
  676. })
  677. //Make the destinaton directory
  678. if !destFshAbs.FileExists(realDest) {
  679. destFshAbs.Mkdir(realDest, 0755)
  680. }
  681. //Start moving
  682. fileCounter := int64(0)
  683. src = filepath.ToSlash(src)
  684. err := srcFshAbs.Walk(src, func(path string, info os.FileInfo, err error) error {
  685. path = filepath.ToSlash(path)
  686. var folderRootRelative string = strings.TrimPrefix(path, src)
  687. if folderRootRelative == "" {
  688. return nil
  689. }
  690. if info.IsDir() {
  691. //Mkdir base on relative path
  692. return destFshAbs.MkdirAll(filepath.Join(realDest, folderRootRelative), 0755)
  693. } else {
  694. //fileCounter++
  695. fileCounter += info.Size()
  696. //Move file base on relative path
  697. fileSrc := filepath.ToSlash(filepath.Join(filepath.Clean(src), folderRootRelative))
  698. fileDest := filepath.ToSlash(filepath.Join(filepath.Clean(realDest), folderRootRelative))
  699. //Update move progress
  700. if progressUpdate != nil {
  701. statusCode := progressUpdate(int(float64(fileCounter)/float64(totalFileCounts)*100), arozfs.Base(fileSrc))
  702. for statusCode == 1 {
  703. //Wait for the task to be resumed
  704. time.Sleep(1 * time.Second)
  705. statusCode = progressUpdate(int(float64(fileCounter)/float64(totalFileCounts)*100), arozfs.Base(fileSrc))
  706. }
  707. if statusCode == 2 {
  708. //Cancel
  709. return errors.New("Operation cancelled by user")
  710. }
  711. }
  712. //Move the file using BLFC
  713. f, err := srcFshAbs.ReadStream(fileSrc)
  714. if err != nil {
  715. log.Println(err)
  716. return err
  717. }
  718. defer f.Close()
  719. err = destFshAbs.WriteStream(fileDest, f, 0755)
  720. if err != nil {
  721. fmt.Println(err)
  722. return err
  723. }
  724. }
  725. return nil
  726. })
  727. return err
  728. }
  729. func BasicFileCopy(src string, dst string) error {
  730. sourceFileStat, err := os.Stat(src)
  731. if err != nil {
  732. return err
  733. }
  734. if !sourceFileStat.Mode().IsRegular() {
  735. return fmt.Errorf("%s is not a regular file", src)
  736. }
  737. source, err := os.Open(src)
  738. if err != nil {
  739. return err
  740. }
  741. defer source.Close()
  742. destination, err := os.Create(dst)
  743. if err != nil {
  744. return err
  745. }
  746. defer destination.Close()
  747. _, err = io.Copy(destination, source)
  748. return err
  749. }
  750. //Use for copying large file using buffering method. Allowing copying large file with little RAM
  751. //Deprecated Since ArozOS v2.000
  752. /*
  753. func BufferedLargeFileCopy(src string, dst string, BUFFERSIZE int64) error {
  754. sourceFileStat, err := os.Stat(src)
  755. if err != nil {
  756. return err
  757. }
  758. if !sourceFileStat.Mode().IsRegular() {
  759. return errors.New("Invalid file source")
  760. }
  761. source, err := os.Open(src)
  762. if err != nil {
  763. return err
  764. }
  765. destination, err := os.Create(dst)
  766. if err != nil {
  767. return err
  768. }
  769. buf := make([]byte, BUFFERSIZE)
  770. for {
  771. n, err := source.Read(buf)
  772. if err != nil && err != io.EOF {
  773. source.Close()
  774. destination.Close()
  775. return err
  776. }
  777. if n == 0 {
  778. source.Close()
  779. destination.Close()
  780. break
  781. }
  782. if _, err := destination.Write(buf[:n]); err != nil {
  783. source.Close()
  784. destination.Close()
  785. return err
  786. }
  787. }
  788. return nil
  789. }
  790. */
  791. // Check if a local path is dir, do not use with file system abstraction realpath
  792. func IsDir(path string) bool {
  793. if !FileExists(path) {
  794. return false
  795. }
  796. fi, err := os.Stat(path)
  797. if err != nil {
  798. log.Fatal(err)
  799. return false
  800. }
  801. switch mode := fi.Mode(); {
  802. case mode.IsDir():
  803. return true
  804. case mode.IsRegular():
  805. return false
  806. }
  807. return false
  808. }
  809. // Unzip tar.gz file, use for unpacking web.tar.gz for lazy people
  810. func ExtractTarGzipFile(filename string, outfile string) error {
  811. f, err := os.Open(filename)
  812. if err != nil {
  813. return err
  814. }
  815. err = ExtractTarGzipByStream(filepath.Clean(outfile), f, true)
  816. if err != nil {
  817. return err
  818. }
  819. return f.Close()
  820. }
  821. func ExtractTarGzipByStream(basedir string, gzipStream io.Reader, onErrorResumeNext bool) error {
  822. uncompressedStream, err := gzip.NewReader(gzipStream)
  823. if err != nil {
  824. return err
  825. }
  826. tarReader := tar.NewReader(uncompressedStream)
  827. for {
  828. header, err := tarReader.Next()
  829. if err == io.EOF {
  830. break
  831. }
  832. if err != nil {
  833. return err
  834. }
  835. switch header.Typeflag {
  836. case tar.TypeDir:
  837. err := os.Mkdir(header.Name, 0755)
  838. if err != nil {
  839. if !onErrorResumeNext {
  840. return err
  841. }
  842. }
  843. case tar.TypeReg:
  844. outFile, err := os.Create(filepath.Join(basedir, header.Name))
  845. if err != nil {
  846. if !onErrorResumeNext {
  847. return err
  848. }
  849. }
  850. _, err = io.Copy(outFile, tarReader)
  851. if err != nil {
  852. if !onErrorResumeNext {
  853. return err
  854. }
  855. }
  856. outFile.Close()
  857. default:
  858. //Unknown filetype, continue
  859. }
  860. }
  861. return nil
  862. }