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.

188 lines
5.6 KiB

  1. package command
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "net/url"
  6. "os"
  7. "path/filepath"
  8. "strings"
  9. "github.com/chrislusf/seaweedfs/weed/operation"
  10. filer_operation "github.com/chrislusf/seaweedfs/weed/operation/filer"
  11. "github.com/chrislusf/seaweedfs/weed/security"
  12. "path"
  13. "net/http"
  14. )
  15. var (
  16. copy CopyOptions
  17. )
  18. type CopyOptions struct {
  19. master *string
  20. include *string
  21. replication *string
  22. collection *string
  23. ttl *string
  24. maxMB *int
  25. secretKey *string
  26. secret security.Secret
  27. }
  28. func init() {
  29. cmdCopy.Run = runCopy // break init cycle
  30. cmdCopy.IsDebug = cmdCopy.Flag.Bool("debug", false, "verbose debug information")
  31. copy.master = cmdCopy.Flag.String("master", "localhost:9333", "SeaweedFS master location")
  32. copy.include = cmdCopy.Flag.String("include", "", "pattens of files to copy, e.g., *.pdf, *.html, ab?d.txt, works together with -dir")
  33. copy.replication = cmdCopy.Flag.String("replication", "", "replication type")
  34. copy.collection = cmdCopy.Flag.String("collection", "", "optional collection name")
  35. copy.ttl = cmdCopy.Flag.String("ttl", "", "time to live, e.g.: 1m, 1h, 1d, 1M, 1y")
  36. copy.maxMB = cmdCopy.Flag.Int("maxMB", 0, "split files larger than the limit")
  37. copy.secretKey = cmdCopy.Flag.String("secure.secret", "", "secret to encrypt Json Web Token(JWT)")
  38. }
  39. var cmdCopy = &Command{
  40. UsageLine: "filer.copy file_or_dir1 [file_or_dir2 file_or_dir3] http://localhost:8888/path/to/a/folder/",
  41. Short: "copy one or a list of files to a filer folder",
  42. Long: `copy one or a list of files, or batch copy one whole folder recursively, to a filer folder
  43. It can copy one or a list of files or folders.
  44. If copying a whole folder recursively:
  45. All files under the folder and subfolders will be copyed.
  46. Optional parameter "-include" allows you to specify the file name patterns.
  47. If any file has a ".gz" extension, the content are considered gzipped already, and will be stored as is.
  48. This can save volume server's gzipped processing and allow customizable gzip compression level.
  49. The file name will strip out ".gz" and stored. For example, "jquery.js.gz" will be stored as "jquery.js".
  50. If "maxMB" is set to a positive number, files larger than it would be split into chunks and copyed separatedly.
  51. The list of file ids of those chunks would be stored in an additional chunk, and this additional chunk's file id would be returned.
  52. `,
  53. }
  54. func runCopy(cmd *Command, args []string) bool {
  55. copy.secret = security.Secret(*copy.secretKey)
  56. if len(args) <= 1 {
  57. return false
  58. }
  59. filerDestination := args[len(args)-1]
  60. fileOrDirs := args[0: len(args)-1]
  61. filerUrl, err := url.Parse(filerDestination)
  62. if err != nil {
  63. fmt.Printf("The last argument should be a URL on filer: %v\n", err)
  64. return false
  65. }
  66. urlPath := filerUrl.Path
  67. if !strings.HasSuffix(urlPath, "/") {
  68. urlPath = urlPath + "/"
  69. }
  70. for _, fileOrDir := range fileOrDirs {
  71. if !doEachCopy(fileOrDir, filerUrl.Host, urlPath) {
  72. return false
  73. }
  74. }
  75. return true
  76. }
  77. func doEachCopy(fileOrDir string, host string, path string) bool {
  78. f, err := os.Open(fileOrDir)
  79. if err != nil {
  80. fmt.Printf("Failed to open file %s: %v\n", fileOrDir, err)
  81. return false
  82. }
  83. defer f.Close()
  84. fi, err := f.Stat()
  85. if err != nil {
  86. fmt.Printf("Failed to get stat for file %s: %v\n", fileOrDir, err)
  87. return false
  88. }
  89. mode := fi.Mode()
  90. if mode.IsDir() {
  91. files, _ := ioutil.ReadDir(fileOrDir)
  92. for _, subFileOrDir := range files {
  93. if !doEachCopy(fileOrDir+"/"+subFileOrDir.Name(), host, path+fi.Name()+"/") {
  94. return false
  95. }
  96. }
  97. return true
  98. }
  99. // this is a regular file
  100. if *copy.include != "" {
  101. if ok, _ := filepath.Match(*copy.include, filepath.Base(fileOrDir)); !ok {
  102. return true
  103. }
  104. }
  105. // find the chunk count
  106. chunkSize := int64(*copy.maxMB * 1024 * 1024)
  107. chunkCount := 1
  108. if chunkSize > 0 && fi.Size() > chunkSize {
  109. chunkCount = int(fi.Size()/chunkSize) + 1
  110. }
  111. // assign a volume
  112. assignResult, err := operation.Assign(*copy.master, &operation.VolumeAssignRequest{
  113. Count: uint64(chunkCount),
  114. Replication: *copy.replication,
  115. Collection: *copy.collection,
  116. Ttl: *copy.ttl,
  117. })
  118. if err != nil {
  119. fmt.Printf("Failed to assign from %s: %v\n", *copy.master, err)
  120. }
  121. if chunkCount == 1 {
  122. return uploadFileAsOne(host, path, assignResult, f, fi)
  123. }
  124. return uploadFileInChunks(host, path, assignResult, f, chunkCount)
  125. }
  126. func uploadFileAsOne(filerUrl string, urlPath string, assignResult *operation.AssignResult, f *os.File, fi os.FileInfo) bool {
  127. // upload the file content
  128. ext := strings.ToLower(path.Ext(f.Name()))
  129. head := make([]byte, 512)
  130. f.Seek(0, 0)
  131. n, err := f.Read(head)
  132. if err != nil {
  133. fmt.Printf("read head of %v: %v\n", f.Name(), err)
  134. return false
  135. }
  136. f.Seek(0, 0)
  137. mimeType := http.DetectContentType(head[:n])
  138. isGzipped := ext == ".gz"
  139. targetUrl := "http://" + assignResult.Url + "/" + assignResult.Fid
  140. uploadResult, err := operation.Upload(targetUrl, f.Name(), f, isGzipped, mimeType, nil, "")
  141. if err != nil {
  142. fmt.Printf("upload data %v to %s: %v\n", f.Name(), targetUrl, err)
  143. return false
  144. }
  145. if uploadResult.Error != "" {
  146. fmt.Printf("upload %v to %s result: %v\n", f.Name(), targetUrl, uploadResult.Error)
  147. return false
  148. }
  149. if err = filer_operation.RegisterFile(filerUrl, filepath.Join(urlPath, f.Name()), assignResult.Fid, fi.Size(),
  150. os.Getuid(), os.Getgid(), copy.secret); err != nil {
  151. fmt.Printf("Failed to register file %s on %s: %v\n", f.Name(), filerUrl, err)
  152. return false
  153. }
  154. fmt.Printf("Copied %s => http://%s%s\n", f.Name(), filerUrl, urlPath)
  155. return true
  156. }
  157. func uploadFileInChunks(filerUrl string, path string, assignResult *operation.AssignResult, f *os.File, chunkCount int) bool {
  158. return false
  159. }