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 8.8 kB

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

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