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

user_space.go 7.4 kB

6 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
7 months ago
6 months ago
6 months ago
7 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package services
  2. import (
  3. "context"
  4. "fmt"
  5. "path"
  6. "strings"
  7. "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
  8. "gitlink.org.cn/cloudream/common/pkgs/logger"
  9. "gitlink.org.cn/cloudream/common/pkgs/trie"
  10. cdssdk "gitlink.org.cn/cloudream/common/sdks/storage"
  11. clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
  12. "gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
  13. "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader/strategy"
  14. stgglb "gitlink.org.cn/cloudream/jcs-pub/common/globals"
  15. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/distlock/reqbuilder"
  16. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
  17. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser"
  18. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/types"
  19. )
  20. type UserSpaceService struct {
  21. *Service
  22. }
  23. func (svc *Service) UserSpaceSvc() *UserSpaceService {
  24. return &UserSpaceService{Service: svc}
  25. }
  26. func (svc *UserSpaceService) Get(userspaceID clitypes.UserSpaceID) (clitypes.UserSpace, error) {
  27. return svc.DB.UserSpace().GetByID(svc.DB.DefCtx(), userspaceID)
  28. }
  29. func (svc *UserSpaceService) GetByName(name string) (clitypes.UserSpace, error) {
  30. return svc.DB.UserSpace().GetByName(svc.DB.DefCtx(), name)
  31. }
  32. func (svc *UserSpaceService) DownloadPackage(packageID clitypes.PackageID, userspaceID clitypes.UserSpaceID, rootPath string) error {
  33. coorCli := stgglb.CoordinatorRPCPool.Get()
  34. defer coorCli.Release()
  35. destStg := svc.UserSpaceMeta.Get(userspaceID)
  36. if destStg == nil {
  37. return fmt.Errorf("userspace not found: %d", userspaceID)
  38. }
  39. details, err := db.DoTx11(svc.DB, svc.DB.Object().GetPackageObjectDetails, packageID)
  40. if err != nil {
  41. return err
  42. }
  43. var pinned []clitypes.ObjectID
  44. plans := exec.NewPlanBuilder()
  45. for _, obj := range details {
  46. strg, err := svc.StrategySelector.Select(strategy.Request{
  47. Detail: obj,
  48. DestLocation: destStg.UserSpace.Storage.GetLocation(),
  49. })
  50. if err != nil {
  51. return fmt.Errorf("select download strategy: %w", err)
  52. }
  53. ft := ioswitch2.NewFromTo()
  54. switch strg := strg.(type) {
  55. case *strategy.DirectStrategy:
  56. ft.AddFrom(ioswitch2.NewFromShardstore(strg.Detail.Object.FileHash, strg.UserSpace, ioswitch2.RawStream()))
  57. case *strategy.ECReconstructStrategy:
  58. for i, b := range strg.Blocks {
  59. ft.AddFrom(ioswitch2.NewFromShardstore(b.FileHash, strg.UserSpaces[i], ioswitch2.ECStream(b.Index)))
  60. ft.ECParam = &strg.Redundancy
  61. }
  62. default:
  63. return fmt.Errorf("unsupported download strategy: %T", strg)
  64. }
  65. ft.AddTo(ioswitch2.NewToBaseStore(*destStg, path.Join(rootPath, obj.Object.Path)))
  66. // 顺便保存到同存储服务的分片存储中
  67. if destStg.UserSpace.ShardStore != nil {
  68. ft.AddTo(ioswitch2.NewToShardStore(*destStg, ioswitch2.RawStream(), ""))
  69. pinned = append(pinned, obj.Object.ObjectID)
  70. }
  71. err = parser.Parse(ft, plans)
  72. if err != nil {
  73. return fmt.Errorf("parse plan: %w", err)
  74. }
  75. }
  76. mutex, err := reqbuilder.NewBuilder().
  77. UserSpace().Buzy(userspaceID).
  78. MutexLock(svc.PubLock)
  79. if err != nil {
  80. return fmt.Errorf("acquire locks failed, err: %w", err)
  81. }
  82. defer mutex.Unlock()
  83. // 记录访问统计
  84. for _, obj := range details {
  85. svc.AccessStat.AddAccessCounter(obj.Object.ObjectID, packageID, userspaceID, 1)
  86. }
  87. exeCtx := exec.NewExecContext()
  88. exec.SetValueByType(exeCtx, svc.StgPool)
  89. drv := plans.Execute(exeCtx)
  90. _, err = drv.Wait(context.Background())
  91. if err != nil {
  92. return err
  93. }
  94. return nil
  95. }
  96. func (svc *UserSpaceService) SpaceToSpace(srcSpaceID clitypes.UserSpaceID, srcPath string, dstSpaceID clitypes.UserSpaceID, dstPath string) (clitypes.SpaceToSpaceResult, error) {
  97. srcSpace := svc.UserSpaceMeta.Get(srcSpaceID)
  98. if srcSpace == nil {
  99. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("source userspace not found: %d", srcSpaceID)
  100. }
  101. srcStore, err := svc.StgPool.GetBaseStore(srcSpace)
  102. if err != nil {
  103. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("get source userspace store: %w", err)
  104. }
  105. dstSpace := svc.UserSpaceMeta.Get(dstSpaceID)
  106. if dstSpace == nil {
  107. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("destination userspace not found: %d", dstSpaceID)
  108. }
  109. dstStore, err := svc.StgPool.GetBaseStore(dstSpace)
  110. if err != nil {
  111. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("get destination userspace store: %w", err)
  112. }
  113. srcPath = strings.Trim(srcPath, cdssdk.ObjectPathSeparator)
  114. dstPath = strings.Trim(dstPath, cdssdk.ObjectPathSeparator)
  115. if srcPath == "" {
  116. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("source path is empty")
  117. }
  118. if dstPath == "" {
  119. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("destination path is empty")
  120. }
  121. entries, cerr := srcStore.ListAll(srcPath)
  122. if cerr != nil {
  123. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("list all from source userspace: %w", cerr)
  124. }
  125. srcPathComps := clitypes.SplitObjectPath(srcPath)
  126. srcDirCompLen := len(srcPathComps) - 1
  127. entryTree := trie.NewTrie[*types.ListEntry]()
  128. for _, e := range entries {
  129. pa, ok := strings.CutSuffix(e.Path, clitypes.ObjectPathSeparator)
  130. comps := clitypes.SplitObjectPath(pa)
  131. e.Path = pa
  132. e2 := e
  133. entryTree.CreateWords(comps[srcDirCompLen:]).Value = &e2
  134. e2.IsDir = e2.IsDir || ok
  135. }
  136. entryTree.Iterate(func(path []string, node *trie.Node[*types.ListEntry], isWordNode bool) trie.VisitCtrl {
  137. if node.Value == nil {
  138. return trie.VisitContinue
  139. }
  140. if node.Value.IsDir && len(node.WordNexts) > 0 {
  141. node.Value = nil
  142. return trie.VisitContinue
  143. }
  144. if !node.Value.IsDir && len(node.WordNexts) == 0 {
  145. node.WordNexts = nil
  146. }
  147. return trie.VisitContinue
  148. })
  149. var filePathes []string
  150. var dirPathes []string
  151. entryTree.Iterate(func(path []string, node *trie.Node[*types.ListEntry], isWordNode bool) trie.VisitCtrl {
  152. if node.Value == nil {
  153. return trie.VisitContinue
  154. }
  155. if node.Value.IsDir {
  156. dirPathes = append(dirPathes, node.Value.Path)
  157. } else {
  158. filePathes = append(filePathes, node.Value.Path)
  159. }
  160. return trie.VisitContinue
  161. })
  162. mutex, err := reqbuilder.NewBuilder().UserSpace().Buzy(srcSpaceID).Buzy(dstSpaceID).MutexLock(svc.PubLock)
  163. if err != nil {
  164. return clitypes.SpaceToSpaceResult{}, fmt.Errorf("acquire lock: %w", err)
  165. }
  166. defer mutex.Unlock()
  167. var success []string
  168. var failed []string
  169. for _, f := range filePathes {
  170. newPath := strings.Replace(f, srcPath, dstPath, 1)
  171. ft := ioswitch2.NewFromTo()
  172. ft.AddFrom(ioswitch2.NewFromBaseStore(*srcSpace, f))
  173. ft.AddTo(ioswitch2.NewToBaseStore(*dstSpace, newPath))
  174. plans := exec.NewPlanBuilder()
  175. err := parser.Parse(ft, plans)
  176. if err != nil {
  177. failed = append(failed, f)
  178. logger.Warnf("s2s: parse plan of file %v: %v", f, err)
  179. continue
  180. }
  181. exeCtx := exec.NewExecContext()
  182. exec.SetValueByType(exeCtx, svc.StgPool)
  183. _, cerr := plans.Execute(exeCtx).Wait(context.Background())
  184. if cerr != nil {
  185. failed = append(failed, f)
  186. logger.Warnf("s2s: execute plan of file %v: %v", f, cerr)
  187. continue
  188. }
  189. success = append(success, f)
  190. }
  191. newDirPathes := make([]string, 0, len(dirPathes))
  192. for i := range dirPathes {
  193. newDirPathes = append(newDirPathes, strings.Replace(dirPathes[i], srcPath, dstPath, 1))
  194. }
  195. for _, d := range newDirPathes {
  196. err := dstStore.Mkdir(d)
  197. if err != nil {
  198. failed = append(failed, d)
  199. } else {
  200. success = append(success, d)
  201. }
  202. }
  203. return clitypes.SpaceToSpaceResult{
  204. Success: success,
  205. Failed: failed,
  206. }, nil
  207. }

本项目旨在将云际存储公共基础设施化,使个人及企业可低门槛使用高效的云际存储服务(安装开箱即用云际存储客户端即可,无需关注其他组件的部署),同时支持用户灵活便捷定制云际存储的功能细节。