You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

650 lines
20 KiB

5 years ago
4 years ago
7 years ago
4 years ago
5 years ago
4 years ago
4 years ago
3 years ago
3 years ago
3 years ago
5 years ago
3 years ago
  1. package command
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/chrislusf/seaweedfs/weed/filer"
  6. "io"
  7. "io/ioutil"
  8. "net/http"
  9. "os"
  10. "path/filepath"
  11. "strconv"
  12. "strings"
  13. "sync"
  14. "time"
  15. "google.golang.org/grpc"
  16. "github.com/chrislusf/seaweedfs/weed/util/grace"
  17. "github.com/chrislusf/seaweedfs/weed/operation"
  18. "github.com/chrislusf/seaweedfs/weed/pb"
  19. "github.com/chrislusf/seaweedfs/weed/pb/filer_pb"
  20. "github.com/chrislusf/seaweedfs/weed/security"
  21. "github.com/chrislusf/seaweedfs/weed/storage/needle"
  22. "github.com/chrislusf/seaweedfs/weed/util"
  23. "github.com/chrislusf/seaweedfs/weed/wdclient"
  24. )
  25. var (
  26. copy CopyOptions
  27. waitGroup sync.WaitGroup
  28. )
  29. type CopyOptions struct {
  30. include *string
  31. replication *string
  32. collection *string
  33. ttl *string
  34. diskType *string
  35. maxMB *int
  36. masterClient *wdclient.MasterClient
  37. concurrenctFiles *int
  38. concurrenctChunks *int
  39. grpcDialOption grpc.DialOption
  40. masters []string
  41. cipher bool
  42. ttlSec int32
  43. checkSize *bool
  44. verbose *bool
  45. }
  46. func init() {
  47. cmdFilerCopy.Run = runCopy // break init cycle
  48. cmdFilerCopy.IsDebug = cmdFilerCopy.Flag.Bool("debug", false, "verbose debug information")
  49. copy.include = cmdFilerCopy.Flag.String("include", "", "pattens of files to copy, e.g., *.pdf, *.html, ab?d.txt, works together with -dir")
  50. copy.replication = cmdFilerCopy.Flag.String("replication", "", "replication type")
  51. copy.collection = cmdFilerCopy.Flag.String("collection", "", "optional collection name")
  52. copy.ttl = cmdFilerCopy.Flag.String("ttl", "", "time to live, e.g.: 1m, 1h, 1d, 1M, 1y")
  53. copy.diskType = cmdFilerCopy.Flag.String("disk", "", "[hdd|ssd|<tag>] hard drive or solid state drive or any tag")
  54. copy.maxMB = cmdFilerCopy.Flag.Int("maxMB", 4, "split files larger than the limit")
  55. copy.concurrenctFiles = cmdFilerCopy.Flag.Int("c", 8, "concurrent file copy goroutines")
  56. copy.concurrenctChunks = cmdFilerCopy.Flag.Int("concurrentChunks", 8, "concurrent chunk copy goroutines for each file")
  57. copy.checkSize = cmdFilerCopy.Flag.Bool("check.size", false, "copy when the target file size is different from the source file")
  58. copy.verbose = cmdFilerCopy.Flag.Bool("verbose", false, "print out details during copying")
  59. }
  60. var cmdFilerCopy = &Command{
  61. UsageLine: "filer.copy file_or_dir1 [file_or_dir2 file_or_dir3] http://localhost:8888/path/to/a/folder/",
  62. Short: "copy one or a list of files to a filer folder",
  63. Long: `copy one or a list of files, or batch copy one whole folder recursively, to a filer folder
  64. It can copy one or a list of files or folders.
  65. If copying a whole folder recursively:
  66. All files under the folder and subfolders will be copyed.
  67. Optional parameter "-include" allows you to specify the file name patterns.
  68. If "maxMB" is set to a positive number, files larger than it would be split into chunks.
  69. `,
  70. }
  71. func runCopy(cmd *Command, args []string) bool {
  72. util.LoadConfiguration("security", false)
  73. if len(args) <= 1 {
  74. return false
  75. }
  76. filerDestination := args[len(args)-1]
  77. fileOrDirs := args[0 : len(args)-1]
  78. filerAddress, urlPath, err := pb.ParseUrl(filerDestination)
  79. if err != nil {
  80. fmt.Printf("The last argument should be a URL on filer: %v\n", err)
  81. return false
  82. }
  83. if !strings.HasSuffix(urlPath, "/") {
  84. fmt.Printf("The last argument should be a folder and end with \"/\"\n")
  85. return false
  86. }
  87. copy.grpcDialOption = security.LoadClientTLS(util.GetViper(), "grpc.client")
  88. masters, collection, replication, dirBuckets, maxMB, cipher, err := readFilerConfiguration(copy.grpcDialOption, filerAddress)
  89. if err != nil {
  90. fmt.Printf("read from filer %s: %v\n", filerAddress, err)
  91. return false
  92. }
  93. if strings.HasPrefix(urlPath, dirBuckets+"/") {
  94. restPath := urlPath[len(dirBuckets)+1:]
  95. if strings.Index(restPath, "/") > 0 {
  96. expectedBucket := restPath[:strings.Index(restPath, "/")]
  97. if *copy.collection == "" {
  98. *copy.collection = expectedBucket
  99. } else if *copy.collection != expectedBucket {
  100. fmt.Printf("destination %s uses collection \"%s\": unexpected collection \"%v\"\n", urlPath, expectedBucket, *copy.collection)
  101. return true
  102. }
  103. }
  104. }
  105. if *copy.collection == "" {
  106. *copy.collection = collection
  107. }
  108. if *copy.replication == "" {
  109. *copy.replication = replication
  110. }
  111. if *copy.maxMB == 0 {
  112. *copy.maxMB = int(maxMB)
  113. }
  114. copy.masters = masters
  115. copy.cipher = cipher
  116. ttl, err := needle.ReadTTL(*copy.ttl)
  117. if err != nil {
  118. fmt.Printf("parsing ttl %s: %v\n", *copy.ttl, err)
  119. return false
  120. }
  121. copy.ttlSec = int32(ttl.Minutes()) * 60
  122. if *cmdFilerCopy.IsDebug {
  123. grace.SetupProfiling("filer.copy.cpu.pprof", "filer.copy.mem.pprof")
  124. }
  125. fileCopyTaskChan := make(chan FileCopyTask, *copy.concurrenctFiles)
  126. go func() {
  127. defer close(fileCopyTaskChan)
  128. for _, fileOrDir := range fileOrDirs {
  129. if err := genFileCopyTask(fileOrDir, urlPath, fileCopyTaskChan); err != nil {
  130. fmt.Fprintf(os.Stderr, "genFileCopyTask : %v\n", err)
  131. break
  132. }
  133. }
  134. }()
  135. for i := 0; i < *copy.concurrenctFiles; i++ {
  136. waitGroup.Add(1)
  137. go func() {
  138. defer waitGroup.Done()
  139. worker := FileCopyWorker{
  140. options: &copy,
  141. filerAddress: filerAddress,
  142. }
  143. if err := worker.copyFiles(fileCopyTaskChan); err != nil {
  144. fmt.Fprintf(os.Stderr, "copy file error: %v\n", err)
  145. return
  146. }
  147. }()
  148. }
  149. waitGroup.Wait()
  150. return true
  151. }
  152. func readFilerConfiguration(grpcDialOption grpc.DialOption, filerGrpcAddress pb.ServerAddress) (masters []string, collection, replication string, dirBuckets string, maxMB uint32, cipher bool, err error) {
  153. err = pb.WithGrpcFilerClient(filerGrpcAddress, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  154. resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
  155. if err != nil {
  156. return fmt.Errorf("get filer %s configuration: %v", filerGrpcAddress, err)
  157. }
  158. masters, collection, replication, maxMB = resp.Masters, resp.Collection, resp.Replication, resp.MaxMb
  159. dirBuckets = resp.DirBuckets
  160. cipher = resp.Cipher
  161. return nil
  162. })
  163. return
  164. }
  165. func genFileCopyTask(fileOrDir string, destPath string, fileCopyTaskChan chan FileCopyTask) error {
  166. fi, err := os.Stat(fileOrDir)
  167. if err != nil {
  168. fmt.Fprintf(os.Stderr, "Error: read file %s: %v\n", fileOrDir, err)
  169. return nil
  170. }
  171. mode := fi.Mode()
  172. uid, gid := util.GetFileUidGid(fi)
  173. fileSize := fi.Size()
  174. if mode.IsDir() {
  175. fileSize = 0
  176. }
  177. fileCopyTaskChan <- FileCopyTask{
  178. sourceLocation: fileOrDir,
  179. destinationUrlPath: destPath,
  180. fileSize: fileSize,
  181. fileMode: fi.Mode(),
  182. uid: uid,
  183. gid: gid,
  184. }
  185. if mode.IsDir() {
  186. files, _ := ioutil.ReadDir(fileOrDir)
  187. for _, subFileOrDir := range files {
  188. cleanedDestDirectory := filepath.Clean(destPath + fi.Name())
  189. if err = genFileCopyTask(fileOrDir+"/"+subFileOrDir.Name(), cleanedDestDirectory+"/", fileCopyTaskChan); err != nil {
  190. return err
  191. }
  192. }
  193. }
  194. return nil
  195. }
  196. type FileCopyWorker struct {
  197. options *CopyOptions
  198. filerAddress pb.ServerAddress
  199. }
  200. func (worker *FileCopyWorker) copyFiles(fileCopyTaskChan chan FileCopyTask) error {
  201. for task := range fileCopyTaskChan {
  202. if err := worker.doEachCopy(task); err != nil {
  203. return err
  204. }
  205. }
  206. return nil
  207. }
  208. type FileCopyTask struct {
  209. sourceLocation string
  210. destinationUrlPath string
  211. fileSize int64
  212. fileMode os.FileMode
  213. uid uint32
  214. gid uint32
  215. }
  216. func (worker *FileCopyWorker) doEachCopy(task FileCopyTask) error {
  217. f, err := os.Open(task.sourceLocation)
  218. if err != nil {
  219. fmt.Printf("Failed to open file %s: %v\n", task.sourceLocation, err)
  220. if _, ok := err.(*os.PathError); ok {
  221. fmt.Printf("skipping %s\n", task.sourceLocation)
  222. return nil
  223. }
  224. return err
  225. }
  226. defer f.Close()
  227. // this is a regular file
  228. if *worker.options.include != "" {
  229. if ok, _ := filepath.Match(*worker.options.include, filepath.Base(task.sourceLocation)); !ok {
  230. return nil
  231. }
  232. }
  233. if shouldCopy, err := worker.checkExistingFileFirst(task, f); err != nil {
  234. return fmt.Errorf("check existing file: %v", err)
  235. } else if !shouldCopy {
  236. if *worker.options.verbose {
  237. fmt.Printf("skipping copied file: %v\n", f.Name())
  238. }
  239. return nil
  240. }
  241. // find the chunk count
  242. chunkSize := int64(*worker.options.maxMB * 1024 * 1024)
  243. chunkCount := 1
  244. if chunkSize > 0 && task.fileSize > chunkSize {
  245. chunkCount = int(task.fileSize/chunkSize) + 1
  246. }
  247. if chunkCount == 1 {
  248. return worker.uploadFileAsOne(task, f)
  249. }
  250. return worker.uploadFileInChunks(task, f, chunkCount, chunkSize)
  251. }
  252. func (worker *FileCopyWorker) checkExistingFileFirst(task FileCopyTask, f *os.File) (shouldCopy bool, err error) {
  253. shouldCopy = true
  254. if !*worker.options.checkSize {
  255. return
  256. }
  257. fileStat, err := f.Stat()
  258. if err != nil {
  259. shouldCopy = false
  260. return
  261. }
  262. err = pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  263. request := &filer_pb.LookupDirectoryEntryRequest{
  264. Directory: task.destinationUrlPath,
  265. Name: filepath.Base(f.Name()),
  266. }
  267. resp, lookupErr := client.LookupDirectoryEntry(context.Background(), request)
  268. if lookupErr != nil {
  269. // mostly not found error
  270. return nil
  271. }
  272. if fileStat.Size() == int64(filer.FileSize(resp.Entry)) {
  273. shouldCopy = false
  274. }
  275. return nil
  276. })
  277. return
  278. }
  279. func (worker *FileCopyWorker) uploadFileAsOne(task FileCopyTask, f *os.File) error {
  280. // upload the file content
  281. fileName := filepath.Base(f.Name())
  282. var mimeType string
  283. var chunks []*filer_pb.FileChunk
  284. var assignResult *filer_pb.AssignVolumeResponse
  285. var assignError error
  286. if task.fileMode&os.ModeDir == 0 && task.fileSize > 0 {
  287. mimeType = detectMimeType(f)
  288. data, err := ioutil.ReadAll(f)
  289. if err != nil {
  290. return err
  291. }
  292. // assign a volume
  293. err = util.Retry("assignVolume", func() error {
  294. return pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  295. request := &filer_pb.AssignVolumeRequest{
  296. Count: 1,
  297. Replication: *worker.options.replication,
  298. Collection: *worker.options.collection,
  299. TtlSec: worker.options.ttlSec,
  300. DiskType: *worker.options.diskType,
  301. Path: task.destinationUrlPath,
  302. }
  303. assignResult, assignError = client.AssignVolume(context.Background(), request)
  304. if assignError != nil {
  305. return fmt.Errorf("assign volume failure %v: %v", request, assignError)
  306. }
  307. if assignResult.Error != "" {
  308. return fmt.Errorf("assign volume failure %v: %v", request, assignResult.Error)
  309. }
  310. if assignResult.Location.Url == "" {
  311. return fmt.Errorf("assign volume failure %v: %v", request, assignResult)
  312. }
  313. return nil
  314. })
  315. })
  316. if err != nil {
  317. return fmt.Errorf("Failed to assign from %v: %v\n", worker.options.masters, err)
  318. }
  319. targetUrl := "http://" + assignResult.Location.Url + "/" + assignResult.FileId
  320. uploadOption := &operation.UploadOption{
  321. UploadUrl: targetUrl,
  322. Filename: fileName,
  323. Cipher: worker.options.cipher,
  324. IsInputCompressed: false,
  325. MimeType: mimeType,
  326. PairMap: nil,
  327. Jwt: security.EncodedJwt(assignResult.Auth),
  328. }
  329. uploadResult, err := operation.UploadData(data, uploadOption)
  330. if err != nil {
  331. return fmt.Errorf("upload data %v to %s: %v\n", fileName, targetUrl, err)
  332. }
  333. if uploadResult.Error != "" {
  334. return fmt.Errorf("upload %v to %s result: %v\n", fileName, targetUrl, uploadResult.Error)
  335. }
  336. if *worker.options.verbose {
  337. fmt.Printf("uploaded %s to %s\n", fileName, targetUrl)
  338. }
  339. chunks = append(chunks, uploadResult.ToPbFileChunk(assignResult.FileId, 0))
  340. fmt.Printf("copied %s => http://%s%s%s\n", f.Name(), worker.filerAddress.ToHttpAddress(), task.destinationUrlPath, fileName)
  341. }
  342. if err := pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  343. request := &filer_pb.CreateEntryRequest{
  344. Directory: task.destinationUrlPath,
  345. Entry: &filer_pb.Entry{
  346. Name: fileName,
  347. Attributes: &filer_pb.FuseAttributes{
  348. Crtime: time.Now().Unix(),
  349. Mtime: time.Now().Unix(),
  350. Gid: task.gid,
  351. Uid: task.uid,
  352. FileSize: uint64(task.fileSize),
  353. FileMode: uint32(task.fileMode),
  354. Mime: mimeType,
  355. Replication: *worker.options.replication,
  356. Collection: *worker.options.collection,
  357. TtlSec: worker.options.ttlSec,
  358. },
  359. Chunks: chunks,
  360. },
  361. }
  362. if err := filer_pb.CreateEntry(client, request); err != nil {
  363. return fmt.Errorf("update fh: %v", err)
  364. }
  365. return nil
  366. }); err != nil {
  367. return fmt.Errorf("upload data %v to http://%s%s%s: %v\n", fileName, worker.filerAddress.ToHttpAddress(), task.destinationUrlPath, fileName, err)
  368. }
  369. return nil
  370. }
  371. func (worker *FileCopyWorker) uploadFileInChunks(task FileCopyTask, f *os.File, chunkCount int, chunkSize int64) error {
  372. fileName := filepath.Base(f.Name())
  373. mimeType := detectMimeType(f)
  374. chunksChan := make(chan *filer_pb.FileChunk, chunkCount)
  375. concurrentChunks := make(chan struct{}, *worker.options.concurrenctChunks)
  376. var wg sync.WaitGroup
  377. var uploadError error
  378. var collection, replication string
  379. fmt.Printf("uploading %s in %d chunks ...\n", fileName, chunkCount)
  380. for i := int64(0); i < int64(chunkCount) && uploadError == nil; i++ {
  381. wg.Add(1)
  382. concurrentChunks <- struct{}{}
  383. go func(i int64) {
  384. defer func() {
  385. wg.Done()
  386. <-concurrentChunks
  387. }()
  388. // assign a volume
  389. var assignResult *filer_pb.AssignVolumeResponse
  390. var assignError error
  391. err := util.Retry("assignVolume", func() error {
  392. return pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  393. request := &filer_pb.AssignVolumeRequest{
  394. Count: 1,
  395. Replication: *worker.options.replication,
  396. Collection: *worker.options.collection,
  397. TtlSec: worker.options.ttlSec,
  398. DiskType: *worker.options.diskType,
  399. Path: task.destinationUrlPath + fileName,
  400. }
  401. assignResult, assignError = client.AssignVolume(context.Background(), request)
  402. if assignError != nil {
  403. return fmt.Errorf("assign volume failure %v: %v", request, assignError)
  404. }
  405. if assignResult.Error != "" {
  406. return fmt.Errorf("assign volume failure %v: %v", request, assignResult.Error)
  407. }
  408. return nil
  409. })
  410. })
  411. if err != nil {
  412. fmt.Printf("Failed to assign from %v: %v\n", worker.options.masters, err)
  413. }
  414. targetUrl := "http://" + assignResult.Location.Url + "/" + assignResult.FileId
  415. if collection == "" {
  416. collection = assignResult.Collection
  417. }
  418. if replication == "" {
  419. replication = assignResult.Replication
  420. }
  421. uploadOption := &operation.UploadOption{
  422. UploadUrl: targetUrl,
  423. Filename: fileName + "-" + strconv.FormatInt(i+1, 10),
  424. Cipher: worker.options.cipher,
  425. IsInputCompressed: false,
  426. MimeType: "",
  427. PairMap: nil,
  428. Jwt: security.EncodedJwt(assignResult.Auth),
  429. }
  430. uploadResult, err, _ := operation.Upload(io.NewSectionReader(f, i*chunkSize, chunkSize), uploadOption)
  431. if err != nil {
  432. uploadError = fmt.Errorf("upload data %v to %s: %v\n", fileName, targetUrl, err)
  433. return
  434. }
  435. if uploadResult.Error != "" {
  436. uploadError = fmt.Errorf("upload %v to %s result: %v\n", fileName, targetUrl, uploadResult.Error)
  437. return
  438. }
  439. chunksChan <- uploadResult.ToPbFileChunk(assignResult.FileId, i*chunkSize)
  440. fmt.Printf("uploaded %s-%d to %s [%d,%d)\n", fileName, i+1, targetUrl, i*chunkSize, i*chunkSize+int64(uploadResult.Size))
  441. }(i)
  442. }
  443. wg.Wait()
  444. close(chunksChan)
  445. var chunks []*filer_pb.FileChunk
  446. for chunk := range chunksChan {
  447. chunks = append(chunks, chunk)
  448. }
  449. if uploadError != nil {
  450. var fileIds []string
  451. for _, chunk := range chunks {
  452. fileIds = append(fileIds, chunk.FileId)
  453. }
  454. operation.DeleteFiles(func() pb.ServerAddress {
  455. return pb.ServerAddress(copy.masters[0])
  456. }, false, worker.options.grpcDialOption, fileIds)
  457. return uploadError
  458. }
  459. manifestedChunks, manifestErr := filer.MaybeManifestize(worker.saveDataAsChunk, chunks)
  460. if manifestErr != nil {
  461. return fmt.Errorf("create manifest: %v", manifestErr)
  462. }
  463. if err := pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  464. request := &filer_pb.CreateEntryRequest{
  465. Directory: task.destinationUrlPath,
  466. Entry: &filer_pb.Entry{
  467. Name: fileName,
  468. Attributes: &filer_pb.FuseAttributes{
  469. Crtime: time.Now().Unix(),
  470. Mtime: time.Now().Unix(),
  471. Gid: task.gid,
  472. Uid: task.uid,
  473. FileSize: uint64(task.fileSize),
  474. FileMode: uint32(task.fileMode),
  475. Mime: mimeType,
  476. Replication: replication,
  477. Collection: collection,
  478. TtlSec: worker.options.ttlSec,
  479. },
  480. Chunks: manifestedChunks,
  481. },
  482. }
  483. if err := filer_pb.CreateEntry(client, request); err != nil {
  484. return fmt.Errorf("update fh: %v", err)
  485. }
  486. return nil
  487. }); err != nil {
  488. return fmt.Errorf("upload data %v to http://%s%s%s: %v\n", fileName, worker.filerAddress.ToHttpAddress(), task.destinationUrlPath, fileName, err)
  489. }
  490. fmt.Printf("copied %s => http://%s%s%s\n", f.Name(), worker.filerAddress.ToHttpAddress(), task.destinationUrlPath, fileName)
  491. return nil
  492. }
  493. func detectMimeType(f *os.File) string {
  494. head := make([]byte, 512)
  495. f.Seek(0, io.SeekStart)
  496. n, err := f.Read(head)
  497. if err == io.EOF {
  498. return ""
  499. }
  500. if err != nil {
  501. fmt.Printf("read head of %v: %v\n", f.Name(), err)
  502. return ""
  503. }
  504. f.Seek(0, io.SeekStart)
  505. mimeType := http.DetectContentType(head[:n])
  506. if mimeType == "application/octet-stream" {
  507. return ""
  508. }
  509. return mimeType
  510. }
  511. func (worker *FileCopyWorker) saveDataAsChunk(reader io.Reader, name string, offset int64) (chunk *filer_pb.FileChunk, collection, replication string, err error) {
  512. var fileId, host string
  513. var auth security.EncodedJwt
  514. if flushErr := pb.WithGrpcFilerClient(worker.filerAddress, worker.options.grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  515. ctx := context.Background()
  516. assignErr := util.Retry("assignVolume", func() error {
  517. request := &filer_pb.AssignVolumeRequest{
  518. Count: 1,
  519. Replication: *worker.options.replication,
  520. Collection: *worker.options.collection,
  521. TtlSec: worker.options.ttlSec,
  522. DiskType: *worker.options.diskType,
  523. Path: name,
  524. }
  525. resp, err := client.AssignVolume(ctx, request)
  526. if err != nil {
  527. return fmt.Errorf("assign volume failure %v: %v", request, err)
  528. }
  529. if resp.Error != "" {
  530. return fmt.Errorf("assign volume failure %v: %v", request, resp.Error)
  531. }
  532. fileId, host, auth = resp.FileId, resp.Location.Url, security.EncodedJwt(resp.Auth)
  533. collection, replication = resp.Collection, resp.Replication
  534. return nil
  535. })
  536. if assignErr != nil {
  537. return assignErr
  538. }
  539. return nil
  540. }); flushErr != nil {
  541. return nil, collection, replication, fmt.Errorf("filerGrpcAddress assign volume: %v", flushErr)
  542. }
  543. uploadOption := &operation.UploadOption{
  544. UploadUrl: fmt.Sprintf("http://%s/%s", host, fileId),
  545. Filename: name,
  546. Cipher: worker.options.cipher,
  547. IsInputCompressed: false,
  548. MimeType: "",
  549. PairMap: nil,
  550. Jwt: auth,
  551. }
  552. uploadResult, flushErr, _ := operation.Upload(reader, uploadOption)
  553. if flushErr != nil {
  554. return nil, collection, replication, fmt.Errorf("upload data: %v", flushErr)
  555. }
  556. if uploadResult.Error != "" {
  557. return nil, collection, replication, fmt.Errorf("upload result: %v", uploadResult.Error)
  558. }
  559. return uploadResult.ToPbFileChunk(fileId, offset), collection, replication, nil
  560. }