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.8 kB

6 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
7 months ago
7 months ago
8 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. package services
  2. import (
  3. "context"
  4. "fmt"
  5. "gitlink.org.cn/cloudream/common/pkgs/ioswitch/exec"
  6. clitypes "gitlink.org.cn/cloudream/jcs-pub/client/types"
  7. "gorm.io/gorm"
  8. "gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
  9. "gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader/strategy"
  10. cliapi "gitlink.org.cn/cloudream/jcs-pub/client/sdk/api/v1"
  11. "gitlink.org.cn/cloudream/jcs-pub/common/ecode"
  12. stgglb "gitlink.org.cn/cloudream/jcs-pub/common/globals"
  13. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2"
  14. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/ioswitch2/parser"
  15. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/publock/reqbuilder"
  16. "gitlink.org.cn/cloudream/jcs-pub/common/pkgs/storage/factory"
  17. )
  18. type UserSpaceService struct {
  19. *Service
  20. }
  21. func (svc *Service) UserSpaceSvc() *UserSpaceService {
  22. return &UserSpaceService{Service: svc}
  23. }
  24. func (svc *UserSpaceService) Get(userspaceID clitypes.UserSpaceID) (clitypes.UserSpace, error) {
  25. return svc.DB.UserSpace().GetByID(svc.DB.DefCtx(), userspaceID)
  26. }
  27. func (svc *UserSpaceService) GetByName(name string) (clitypes.UserSpace, error) {
  28. return svc.DB.UserSpace().GetByName(svc.DB.DefCtx(), name)
  29. }
  30. func (svc *UserSpaceService) Create(req cliapi.UserSpaceCreate) (*cliapi.UserSpaceCreateResp, *ecode.CodeError) {
  31. db2 := svc.DB
  32. space, err := db.DoTx01(db2, func(tx db.SQLContext) (clitypes.UserSpace, error) {
  33. space, err := db2.UserSpace().GetByName(tx, req.Name)
  34. if err == nil {
  35. return clitypes.UserSpace{}, gorm.ErrDuplicatedKey
  36. }
  37. if err != gorm.ErrRecordNotFound {
  38. return clitypes.UserSpace{}, err
  39. }
  40. space = clitypes.UserSpace{
  41. Name: req.Name,
  42. Storage: req.Storage,
  43. Credential: req.Credential,
  44. ShardStore: req.ShardStore,
  45. Features: req.Features,
  46. WorkingDir: clitypes.PathFromJcsPathString(req.WorkingDir),
  47. Revision: 0,
  48. }
  49. err = db2.UserSpace().Create(tx, &space)
  50. if err != nil {
  51. return clitypes.UserSpace{}, err
  52. }
  53. return space, nil
  54. })
  55. if err == gorm.ErrDuplicatedKey {
  56. return nil, ecode.New(ecode.DataExists, "user space name already exists")
  57. }
  58. if err != nil {
  59. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  60. }
  61. return &cliapi.UserSpaceCreateResp{UserSpace: space}, nil
  62. }
  63. func (svc *UserSpaceService) Update(req cliapi.UserSpaceUpdate) (*cliapi.UserSpaceUpdateResp, *ecode.CodeError) {
  64. db2 := svc.DB
  65. space, err := db.DoTx01(db2, func(tx db.SQLContext) (clitypes.UserSpace, error) {
  66. space, err := db2.UserSpace().GetByID(tx, req.UserSpaceID)
  67. if err != nil {
  68. return clitypes.UserSpace{}, err
  69. }
  70. if space.Name != req.Name {
  71. _, err = db2.UserSpace().GetByName(tx, req.Name)
  72. if err == nil {
  73. return clitypes.UserSpace{}, gorm.ErrDuplicatedKey
  74. }
  75. if err != gorm.ErrRecordNotFound {
  76. return clitypes.UserSpace{}, err
  77. }
  78. }
  79. space.Name = req.Name
  80. space.Credential = req.Credential
  81. space.Features = req.Features
  82. space.Revision += 1
  83. return space, db2.UserSpace().UpdateColumns(tx, space, "Name", "Credential", "Features", "Revision")
  84. })
  85. if err == gorm.ErrDuplicatedKey {
  86. return nil, ecode.New(ecode.DataExists, "user space name already exists")
  87. }
  88. if err != nil {
  89. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  90. }
  91. // 通知元数据缓存无效
  92. svc.UserSpaceMeta.Drop([]clitypes.UserSpaceID{req.UserSpaceID})
  93. // 通知存储服务组件池停止组件。TODO 对于在Hub上运行的组件,需要一个机制去定时清理
  94. svc.StgPool.Drop(stgglb.UserID, space.UserSpaceID)
  95. // TODO 考虑加锁再进行操作
  96. return &cliapi.UserSpaceUpdateResp{UserSpace: space}, nil
  97. }
  98. func (svc *UserSpaceService) Delete(req cliapi.UserSpaceDelete) (*cliapi.UserSpaceDeleteResp, *ecode.CodeError) {
  99. db2 := svc.DB
  100. err := db2.DoTx(func(tx db.SQLContext) error {
  101. err := db2.UserSpace().DeleteByID(tx, req.UserSpaceID)
  102. if err != nil {
  103. return err
  104. }
  105. err = db2.ObjectBlock().DeleteByUserSpaceID(tx, req.UserSpaceID)
  106. if err != nil {
  107. return err
  108. }
  109. err = db2.PinnedObject().DeleteByUserSpaceID(tx, req.UserSpaceID)
  110. if err != nil {
  111. return err
  112. }
  113. err = db2.ObjectAccessStat().DeleteByUserSpaceID(tx, req.UserSpaceID)
  114. if err != nil {
  115. return err
  116. }
  117. err = db2.PackageAccessStat().DeleteByUserSpaceID(tx, req.UserSpaceID)
  118. if err != nil {
  119. return err
  120. }
  121. return nil
  122. })
  123. if err != nil {
  124. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  125. }
  126. // 通知元数据缓存无效
  127. svc.UserSpaceMeta.Drop([]clitypes.UserSpaceID{req.UserSpaceID})
  128. // 通知存储服务组件池停止组件。TODO 对于在Hub上运行的组件,需要一个机制去定时清理
  129. svc.StgPool.Drop(stgglb.UserID, req.UserSpaceID)
  130. // TODO 考虑加锁再进行操作,并且增加机制打断已经在进行的操作。
  131. return &cliapi.UserSpaceDeleteResp{}, nil
  132. }
  133. func (svc *UserSpaceService) Test(req cliapi.UserSpaceTest) (*cliapi.UserSpaceTestResp, *ecode.CodeError) {
  134. detail := clitypes.UserSpaceDetail{
  135. UserID: stgglb.UserID,
  136. UserSpace: clitypes.UserSpace{
  137. Name: "test",
  138. Storage: req.Storage,
  139. Credential: req.Credential,
  140. WorkingDir: clitypes.PathFromJcsPathString(req.WorikingDir),
  141. },
  142. }
  143. blder := factory.GetBuilder(&detail)
  144. baseStore, err := blder.CreateBaseStore(false)
  145. if err != nil {
  146. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  147. }
  148. err = baseStore.Test()
  149. if err != nil {
  150. return nil, ecode.Newf(ecode.OperationFailed, "%v", err)
  151. }
  152. return &cliapi.UserSpaceTestResp{}, nil
  153. }
  154. func (svc *UserSpaceService) DownloadPackage(packageID clitypes.PackageID, userspaceID clitypes.UserSpaceID, rootPath string) error {
  155. coorCli := stgglb.CoordinatorRPCPool.Get()
  156. defer coorCli.Release()
  157. destStg := svc.UserSpaceMeta.Get(userspaceID)
  158. if destStg == nil {
  159. return fmt.Errorf("userspace not found: %d", userspaceID)
  160. }
  161. details, err := db.DoTx11(svc.DB, svc.DB.Object().GetPackageObjectDetails, packageID)
  162. if err != nil {
  163. return err
  164. }
  165. rootJPath := clitypes.PathFromJcsPathString(rootPath)
  166. var pinned []clitypes.ObjectID
  167. plans := exec.NewPlanBuilder()
  168. for _, obj := range details {
  169. strg, err := svc.StrategySelector.Select(strategy.Request{
  170. Detail: obj,
  171. DestLocation: destStg.UserSpace.Storage.GetLocation(),
  172. })
  173. if err != nil {
  174. return fmt.Errorf("select download strategy: %w", err)
  175. }
  176. ft := ioswitch2.NewFromTo()
  177. switch strg := strg.(type) {
  178. case *strategy.DirectStrategy:
  179. ft.AddFrom(ioswitch2.NewFromShardstore(strg.Detail.Object.FileHash, strg.UserSpace, ioswitch2.RawStream()))
  180. case *strategy.ECReconstructStrategy:
  181. for i, b := range strg.Blocks {
  182. ft.AddFrom(ioswitch2.NewFromShardstore(b.FileHash, strg.UserSpaces[i], ioswitch2.ECStream(b.Index)))
  183. ft.ECParam = &strg.Redundancy
  184. }
  185. default:
  186. return fmt.Errorf("unsupported download strategy: %T", strg)
  187. }
  188. objPath := clitypes.PathFromJcsPathString(obj.Object.Path)
  189. dstPath := rootJPath.ConcatNew(objPath)
  190. ft.AddTo(ioswitch2.NewToBaseStore(*destStg, dstPath))
  191. // 顺便保存到同存储服务的分片存储中
  192. if destStg.UserSpace.ShardStore != nil {
  193. ft.AddTo(ioswitch2.NewToShardStore(*destStg, ioswitch2.RawStream(), ""))
  194. pinned = append(pinned, obj.Object.ObjectID)
  195. }
  196. err = parser.Parse(ft, plans)
  197. if err != nil {
  198. return fmt.Errorf("parse plan: %w", err)
  199. }
  200. }
  201. mutex, err := reqbuilder.NewBuilder().
  202. UserSpace().Buzy(userspaceID).
  203. MutexLock(svc.PubLock)
  204. if err != nil {
  205. return fmt.Errorf("acquire locks failed, err: %w", err)
  206. }
  207. defer mutex.Unlock()
  208. // 记录访问统计
  209. for _, obj := range details {
  210. svc.AccessStat.AddAccessCounter(obj.Object.ObjectID, packageID, userspaceID, 1)
  211. }
  212. exeCtx := exec.NewExecContext()
  213. exec.SetValueByType(exeCtx, svc.StgPool)
  214. drv := plans.Execute(exeCtx)
  215. _, err = drv.Wait(context.Background())
  216. if err != nil {
  217. return err
  218. }
  219. return nil
  220. }

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