fileOpr.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  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. // Call the new function with default compression level (e.g., 6)
  297. return ArozZipFileWithCompressionLevel(sourceFshs, filelist, outputFsh, outputfile, includeTopLevelFolder, flate.DefaultCompression)
  298. }
  299. func ArozZipFileWithCompressionLevel(sourceFshs []*FileSystemHandler, filelist []string, outputFsh *FileSystemHandler, outputfile string, includeTopLevelFolder bool, compressionLevel int) error {
  300. //Create the target zip file
  301. var file arozfs.File
  302. var err error
  303. if outputFsh != nil {
  304. file, err = outputFsh.FileSystemAbstraction.Create(outputfile)
  305. } else {
  306. //Force local fs
  307. file, err = os.Create(outputfile)
  308. }
  309. if err != nil {
  310. return err
  311. }
  312. defer file.Close()
  313. writer := zip.NewWriter(file)
  314. defer writer.Close()
  315. writer.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
  316. return flate.NewWriter(out, compressionLevel)
  317. })
  318. for i, srcpath := range filelist {
  319. thisFsh := sourceFshs[i]
  320. var fshAbs FileSystemAbstraction
  321. if thisFsh == nil {
  322. //Use local fs functions
  323. if IsDir(srcpath) {
  324. //This is a directory
  325. topLevelFolderName := filepath.ToSlash(arozfs.Base(filepath.Dir(srcpath)) + "/" + arozfs.Base(srcpath))
  326. err = filepath.Walk(srcpath, func(path string, info os.FileInfo, err error) error {
  327. if err != nil {
  328. return err
  329. }
  330. if info.IsDir() {
  331. return nil
  332. }
  333. if insideHiddenFolder(path) {
  334. //This is hidden file / folder. Skip this
  335. return nil
  336. }
  337. thisFile, err := os.Open(path)
  338. if err != nil {
  339. return err
  340. }
  341. defer thisFile.Close()
  342. relativePath := strings.ReplaceAll(filepath.ToSlash(path), filepath.ToSlash(filepath.Clean(srcpath))+"/", "")
  343. if includeTopLevelFolder {
  344. relativePath = topLevelFolderName + "/" + relativePath
  345. } else {
  346. relativePath = arozfs.Base(srcpath) + "/" + relativePath
  347. }
  348. f, err := writer.Create(relativePath)
  349. if err != nil {
  350. return err
  351. }
  352. _, err = io.Copy(f, thisFile)
  353. if err != nil {
  354. return err
  355. }
  356. return nil
  357. })
  358. if err != nil {
  359. return err
  360. }
  361. } else {
  362. //This is a file
  363. topLevelFolderName := arozfs.Base(filepath.Dir(srcpath))
  364. thisFile, err := os.Open(srcpath)
  365. if err != nil {
  366. return err
  367. }
  368. defer thisFile.Close()
  369. relativePath := arozfs.Base(srcpath)
  370. if includeTopLevelFolder {
  371. relativePath = topLevelFolderName + "/" + relativePath
  372. }
  373. f, err := writer.Create(relativePath)
  374. if err != nil {
  375. return err
  376. }
  377. _, err = io.Copy(f, thisFile)
  378. if err != nil {
  379. return err
  380. }
  381. }
  382. } else {
  383. //Use file system abstraction
  384. fshAbs = thisFsh.FileSystemAbstraction
  385. if fshAbs.IsDir(srcpath) {
  386. //This is a directory
  387. topLevelFolderName := filepath.ToSlash(arozfs.Base(filepath.Dir(srcpath)) + "/" + arozfs.Base(srcpath))
  388. err = fshAbs.Walk(srcpath, func(path string, info os.FileInfo, err error) error {
  389. if err != nil {
  390. return err
  391. }
  392. if info.IsDir() {
  393. return nil
  394. }
  395. if insideHiddenFolder(path) {
  396. //This is hidden file / folder. Skip this
  397. return nil
  398. }
  399. thisFile, err := fshAbs.ReadStream(path)
  400. if err != nil {
  401. fmt.Println(err)
  402. return err
  403. }
  404. defer thisFile.Close()
  405. relativePath := strings.ReplaceAll(filepath.ToSlash(path), filepath.ToSlash(filepath.Clean(srcpath))+"/", "")
  406. if includeTopLevelFolder {
  407. relativePath = topLevelFolderName + "/" + relativePath
  408. } else {
  409. relativePath = arozfs.Base(srcpath) + "/" + relativePath
  410. }
  411. f, err := writer.Create(relativePath)
  412. if err != nil {
  413. return err
  414. }
  415. _, err = io.Copy(f, thisFile)
  416. if err != nil {
  417. return err
  418. }
  419. return nil
  420. })
  421. if err != nil {
  422. return err
  423. }
  424. } else {
  425. //This is a file
  426. topLevelFolderName := arozfs.Base(filepath.Dir(srcpath))
  427. thisFile, err := fshAbs.ReadStream(srcpath)
  428. if err != nil {
  429. return err
  430. }
  431. defer thisFile.Close()
  432. relativePath := arozfs.Base(srcpath)
  433. if includeTopLevelFolder {
  434. relativePath = topLevelFolderName + "/" + relativePath
  435. }
  436. f, err := writer.Create(relativePath)
  437. if err != nil {
  438. return err
  439. }
  440. _, err = io.Copy(f, thisFile)
  441. if err != nil {
  442. return err
  443. }
  444. }
  445. }
  446. }
  447. return nil
  448. }
  449. func insideHiddenFolder(path string) bool {
  450. FileIsHidden, err := hidden.IsHidden(path, true)
  451. if err != nil {
  452. //Read error. Maybe permission issue, assuem is hidden
  453. return true
  454. }
  455. return FileIsHidden
  456. }
  457. func ViewZipFile(filepath string) ([]string, error) {
  458. z := archiver.Zip{}
  459. filelist := []string{}
  460. err := z.Walk(filepath, func(f archiver.File) error {
  461. filelist = append(filelist, f.Name())
  462. return nil
  463. })
  464. return filelist, err
  465. }
  466. func FileCopy(srcFsh *FileSystemHandler, src string, destFsh *FileSystemHandler, dest string, mode string, progressUpdate func(int, string) int) error {
  467. srcFshAbs := srcFsh.FileSystemAbstraction
  468. destFshAbs := destFsh.FileSystemAbstraction
  469. if srcFshAbs.IsDir(src) && strings.HasPrefix(dest, src) {
  470. //Recursive operation. Reject
  471. return errors.New("Recursive copy operation.")
  472. }
  473. //Check if the copy destination file already have an identical file
  474. copiedFilename := arozfs.Base(src)
  475. if destFshAbs.FileExists(filepath.Join(dest, arozfs.Base(src))) {
  476. if mode == "" {
  477. //Do not specific file exists principle
  478. return errors.New("Destination file already exists.")
  479. } else if mode == "skip" {
  480. //Skip this file
  481. return nil
  482. } else if mode == "overwrite" {
  483. //Continue with the following code
  484. //Check if the copy and paste dest are identical
  485. if filepath.ToSlash(filepath.Clean(src)) == filepath.ToSlash(filepath.Clean(filepath.Join(dest, arozfs.Base(src)))) {
  486. //Source and target identical. Cannot overwrite.
  487. return errors.New("Source and destination paths are identical.")
  488. }
  489. } else if mode == "keep" {
  490. //Keep the file but saved with 'Copy' suffix
  491. newFilename := strings.TrimSuffix(arozfs.Base(src), filepath.Ext(src)) + " - Copy" + filepath.Ext(src)
  492. //Check if the newFilename already exists. If yes, continue adding suffix
  493. duplicateCounter := 0
  494. for destFshAbs.FileExists(filepath.Join(dest, newFilename)) {
  495. duplicateCounter++
  496. newFilename = strings.TrimSuffix(arozfs.Base(src), filepath.Ext(src)) + " - Copy(" + strconv.Itoa(duplicateCounter) + ")" + filepath.Ext(src)
  497. if duplicateCounter > 1024 {
  498. //Maxmium loop encountered. For thread safty, terminate here
  499. return errors.New("Too many copies of identical files.")
  500. }
  501. }
  502. copiedFilename = newFilename
  503. } else {
  504. //This exists opr not supported.
  505. return errors.New("Unknown file exists rules given.")
  506. }
  507. }
  508. //Ready to move. Check if both folder are located in the same root devices. If not, use copy and delete method.
  509. if srcFshAbs.IsDir(src) {
  510. //Source file is directory. CopyFolder
  511. realDest := filepath.Join(dest, copiedFilename)
  512. err := dirCopy(srcFsh, src, destFsh, realDest, progressUpdate)
  513. if err != nil {
  514. return err
  515. }
  516. } else {
  517. //Source is file only. Copy file.
  518. realDest := filepath.Join(dest, copiedFilename)
  519. f, err := srcFshAbs.ReadStream(src)
  520. if err != nil {
  521. return err
  522. }
  523. defer f.Close()
  524. err = destFshAbs.WriteStream(realDest, f, 0775)
  525. if err != nil {
  526. return err
  527. }
  528. if progressUpdate != nil {
  529. //Set progress to 100, leave it to upper level abstraction to handle
  530. statusCode := progressUpdate(100, arozfs.Base(realDest))
  531. for statusCode == 1 {
  532. //Wait for the task to be resumed
  533. time.Sleep(1 * time.Second)
  534. statusCode = progressUpdate(100, arozfs.Base(realDest))
  535. }
  536. if statusCode == 2 {
  537. //Cancel
  538. return errors.New("Operation cancelled by user")
  539. }
  540. }
  541. }
  542. return nil
  543. }
  544. func FileMove(srcFsh *FileSystemHandler, src string, destFsh *FileSystemHandler, dest string, mode string, fastMove bool, progressUpdate func(int, string) int) error {
  545. srcAbst := srcFsh.FileSystemAbstraction
  546. destAbst := destFsh.FileSystemAbstraction
  547. src = filepath.ToSlash(src)
  548. dest = filepath.ToSlash(dest)
  549. if srcAbst.IsDir(src) && strings.HasPrefix(dest, src) {
  550. //Recursive operation. Reject
  551. return errors.New("Recursive move operation.")
  552. }
  553. if !destAbst.FileExists(dest) {
  554. if destAbst.FileExists(filepath.Dir(dest)) {
  555. //User pass in the whole path for the folder. Report error usecase.
  556. return errors.New("Dest location should be an existing folder instead of the full path of the moved file.")
  557. }
  558. os.MkdirAll(dest, 0775)
  559. }
  560. //Check if the target file already exists.
  561. movedFilename := arozfs.Base(src)
  562. if destAbst.FileExists(filepath.Join(dest, arozfs.Base(src))) {
  563. //Handle cases where file already exists
  564. if mode == "" {
  565. //Do not specific file exists principle
  566. return errors.New("Destination file already exists.")
  567. } else if mode == "skip" {
  568. //Skip this file
  569. return nil
  570. } else if mode == "overwrite" {
  571. //Continue with the following code
  572. //Check if the copy and paste dest are identical
  573. if filepath.ToSlash(filepath.Clean(src)) == filepath.ToSlash(filepath.Clean(filepath.Join(dest, arozfs.Base(src)))) {
  574. //Source and target identical. Cannot overwrite.
  575. return errors.New("Source and destination paths are identical.")
  576. }
  577. } else if mode == "keep" {
  578. //Keep the file but saved with 'Copy' suffix
  579. newFilename := strings.TrimSuffix(arozfs.Base(src), filepath.Ext(src)) + " - Copy" + filepath.Ext(src)
  580. //Check if the newFilename already exists. If yes, continue adding suffix
  581. duplicateCounter := 0
  582. for destAbst.FileExists(filepath.Join(dest, newFilename)) {
  583. duplicateCounter++
  584. newFilename = strings.TrimSuffix(arozfs.Base(src), filepath.Ext(src)) + " - Copy(" + strconv.Itoa(duplicateCounter) + ")" + filepath.Ext(src)
  585. if duplicateCounter > 1024 {
  586. //Maxmium loop encountered. For thread safty, terminate here
  587. return errors.New("Too many copies of identical files.")
  588. }
  589. }
  590. movedFilename = newFilename
  591. } else {
  592. //This exists opr not supported.
  593. return errors.New("Unknown file exists rules given.")
  594. }
  595. }
  596. if fastMove {
  597. //Ready to move with the quick rename method
  598. realDest := filepath.Join(dest, movedFilename)
  599. err := os.Rename(src, realDest)
  600. if err == nil {
  601. //Fast move success
  602. return nil
  603. }
  604. //Fast move failed. Back to the original copy and move method
  605. }
  606. //Ready to move. Check if both folder are located in the same root devices. If not, use copy and delete method.
  607. if srcAbst.IsDir(src) {
  608. //Source file is directory. CopyFolder
  609. realDest := filepath.Join(dest, movedFilename)
  610. //err := dircpy.Copy(src, realDest)
  611. err := dirCopy(srcFsh, src, destFsh, realDest, progressUpdate)
  612. if err != nil {
  613. return err
  614. } else {
  615. //Move completed. Remove source file.
  616. srcAbst.RemoveAll(src)
  617. return nil
  618. }
  619. } else {
  620. //Source is file only. Copy file.
  621. realDest := filepath.Join(dest, movedFilename)
  622. /*
  623. Updates 20-10-2020, replaced io.Copy to BufferedLargeFileCopy
  624. Legacy code removed.
  625. */
  626. //Update the progress
  627. if progressUpdate != nil {
  628. statusCode := progressUpdate(100, arozfs.Base(src))
  629. for statusCode == 1 {
  630. //Wait for the task to be resumed
  631. time.Sleep(1 * time.Second)
  632. statusCode = progressUpdate(100, arozfs.Base(realDest))
  633. }
  634. if statusCode == 2 {
  635. //Cancel
  636. return errors.New("Operation cancelled by user")
  637. }
  638. }
  639. f, err := srcAbst.ReadStream(src)
  640. if err != nil {
  641. fmt.Println(err)
  642. return err
  643. }
  644. defer f.Close()
  645. err = destAbst.WriteStream(realDest, f, 0755)
  646. if err != nil {
  647. fmt.Println(err)
  648. return err
  649. }
  650. //Delete the source file after copy
  651. err = srcAbst.Remove(src)
  652. counter := 0
  653. for err != nil {
  654. //Sometime Windows need this to prevent windows caching bring problems to file remove
  655. time.Sleep(1 * time.Second)
  656. os.Remove(src)
  657. counter++
  658. log.Println("Retrying to remove file: " + src)
  659. if counter > 10 {
  660. return errors.New("Source file remove failed.")
  661. }
  662. }
  663. }
  664. return nil
  665. }
  666. // Copy a given directory, with no progress udpate
  667. func CopyDir(srcFsh *FileSystemHandler, src string, destFsh *FileSystemHandler, dest string) error {
  668. return dirCopy(srcFsh, src, destFsh, dest, func(progress int, name string) int { return 0 })
  669. }
  670. // Replacment of the legacy dirCopy plugin with filepath.Walk function. Allowing real time progress update to front end
  671. func dirCopy(srcFsh *FileSystemHandler, src string, destFsh *FileSystemHandler, realDest string, progressUpdate func(int, string) int) error {
  672. srcFshAbs := srcFsh.FileSystemAbstraction
  673. destFshAbs := destFsh.FileSystemAbstraction
  674. //Get the total file counts
  675. totalFileCounts := int64(0)
  676. srcFshAbs.Walk(src, func(path string, info os.FileInfo, err error) error {
  677. if !info.IsDir() {
  678. //Updates 22 April 2021, chnaged from file count to file size for progress update
  679. //totalFileCounts++
  680. totalFileCounts += info.Size()
  681. }
  682. return nil
  683. })
  684. //Make the destinaton directory
  685. if !destFshAbs.FileExists(realDest) {
  686. destFshAbs.Mkdir(realDest, 0755)
  687. }
  688. //Start moving
  689. fileCounter := int64(0)
  690. src = filepath.ToSlash(src)
  691. err := srcFshAbs.Walk(src, func(path string, info os.FileInfo, err error) error {
  692. path = filepath.ToSlash(path)
  693. var folderRootRelative string = strings.TrimPrefix(path, src)
  694. if folderRootRelative == "" {
  695. return nil
  696. }
  697. if info.IsDir() {
  698. //Mkdir base on relative path
  699. return destFshAbs.MkdirAll(filepath.Join(realDest, folderRootRelative), 0755)
  700. } else {
  701. //fileCounter++
  702. fileCounter += info.Size()
  703. //Move file base on relative path
  704. fileSrc := filepath.ToSlash(filepath.Join(filepath.Clean(src), folderRootRelative))
  705. fileDest := filepath.ToSlash(filepath.Join(filepath.Clean(realDest), folderRootRelative))
  706. //Update move progress
  707. if progressUpdate != nil {
  708. statusCode := progressUpdate(int(float64(fileCounter)/float64(totalFileCounts)*100), arozfs.Base(fileSrc))
  709. for statusCode == 1 {
  710. //Wait for the task to be resumed
  711. time.Sleep(1 * time.Second)
  712. statusCode = progressUpdate(int(float64(fileCounter)/float64(totalFileCounts)*100), arozfs.Base(fileSrc))
  713. }
  714. if statusCode == 2 {
  715. //Cancel
  716. return errors.New("Operation cancelled by user")
  717. }
  718. }
  719. //Move the file using BLFC
  720. f, err := srcFshAbs.ReadStream(fileSrc)
  721. if err != nil {
  722. log.Println(err)
  723. return err
  724. }
  725. defer f.Close()
  726. err = destFshAbs.WriteStream(fileDest, f, 0755)
  727. if err != nil {
  728. fmt.Println(err)
  729. return err
  730. }
  731. }
  732. return nil
  733. })
  734. return err
  735. }
  736. func BasicFileCopy(src string, dst string) error {
  737. sourceFileStat, err := os.Stat(src)
  738. if err != nil {
  739. return err
  740. }
  741. if !sourceFileStat.Mode().IsRegular() {
  742. return fmt.Errorf("%s is not a regular file", src)
  743. }
  744. source, err := os.Open(src)
  745. if err != nil {
  746. return err
  747. }
  748. defer source.Close()
  749. destination, err := os.Create(dst)
  750. if err != nil {
  751. return err
  752. }
  753. defer destination.Close()
  754. _, err = io.Copy(destination, source)
  755. return err
  756. }
  757. //Use for copying large file using buffering method. Allowing copying large file with little RAM
  758. //Deprecated Since ArozOS v2.000
  759. /*
  760. func BufferedLargeFileCopy(src string, dst string, BUFFERSIZE int64) error {
  761. sourceFileStat, err := os.Stat(src)
  762. if err != nil {
  763. return err
  764. }
  765. if !sourceFileStat.Mode().IsRegular() {
  766. return errors.New("Invalid file source")
  767. }
  768. source, err := os.Open(src)
  769. if err != nil {
  770. return err
  771. }
  772. destination, err := os.Create(dst)
  773. if err != nil {
  774. return err
  775. }
  776. buf := make([]byte, BUFFERSIZE)
  777. for {
  778. n, err := source.Read(buf)
  779. if err != nil && err != io.EOF {
  780. source.Close()
  781. destination.Close()
  782. return err
  783. }
  784. if n == 0 {
  785. source.Close()
  786. destination.Close()
  787. break
  788. }
  789. if _, err := destination.Write(buf[:n]); err != nil {
  790. source.Close()
  791. destination.Close()
  792. return err
  793. }
  794. }
  795. return nil
  796. }
  797. */
  798. // Check if a local path is dir, do not use with file system abstraction realpath
  799. func IsDir(path string) bool {
  800. if !FileExists(path) {
  801. return false
  802. }
  803. fi, err := os.Stat(path)
  804. if err != nil {
  805. log.Fatal(err)
  806. return false
  807. }
  808. switch mode := fi.Mode(); {
  809. case mode.IsDir():
  810. return true
  811. case mode.IsRegular():
  812. return false
  813. }
  814. return false
  815. }
  816. // Unzip tar.gz file, use for unpacking web.tar.gz for lazy people
  817. func ExtractTarGzipFile(filename string, outfile string) error {
  818. f, err := os.Open(filename)
  819. if err != nil {
  820. return err
  821. }
  822. err = ExtractTarGzipByStream(filepath.Clean(outfile), f, true)
  823. if err != nil {
  824. return err
  825. }
  826. return f.Close()
  827. }
  828. func ExtractTarGzipByStream(basedir string, gzipStream io.Reader, onErrorResumeNext bool) error {
  829. uncompressedStream, err := gzip.NewReader(gzipStream)
  830. if err != nil {
  831. return err
  832. }
  833. tarReader := tar.NewReader(uncompressedStream)
  834. for {
  835. header, err := tarReader.Next()
  836. if err == io.EOF {
  837. break
  838. }
  839. if err != nil {
  840. return err
  841. }
  842. switch header.Typeflag {
  843. case tar.TypeDir:
  844. err := os.Mkdir(header.Name, 0755)
  845. if err != nil {
  846. if !onErrorResumeNext {
  847. return err
  848. }
  849. }
  850. case tar.TypeReg:
  851. outFile, err := os.Create(filepath.Join(basedir, header.Name))
  852. if err != nil {
  853. if !onErrorResumeNext {
  854. return err
  855. }
  856. }
  857. _, err = io.Copy(outFile, tarReader)
  858. if err != nil {
  859. if !onErrorResumeNext {
  860. return err
  861. }
  862. }
  863. outFile.Close()
  864. default:
  865. //Unknown filetype, continue
  866. }
  867. }
  868. return nil
  869. }