package share import ( "context" "fmt" "net/http" "path" model "github.com/cloudreve/Cloudreve/v3/models" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" "github.com/cloudreve/Cloudreve/v3/pkg/hashid" "github.com/cloudreve/Cloudreve/v3/pkg/serializer" "github.com/cloudreve/Cloudreve/v3/pkg/util" "github.com/cloudreve/Cloudreve/v3/service/explorer" "github.com/gin-gonic/gin" ) // ShareUserGetService 获取用户的分享服务 type ShareUserGetService struct { Type string `form:"type" binding:"required,eq=hot|eq=default"` Page uint `form:"page" binding:"required,min=1"` } // ShareGetService 获取分享服务 type ShareGetService struct { Password string `form:"password" binding:"max=255"` } // Service 对分享进行操作的服务, // path 为可选文件完整路径,在目录分享下有效 type Service struct { Path string `form:"path" uri:"path" binding:"max=65535"` } // ArchiveService 分享归档下载服务 type ArchiveService struct { Path string `json:"path" binding:"required,max=65535"` Items []string `json:"items"` Dirs []string `json:"dirs"` } // ShareListService 列出分享 type ShareListService struct { Page uint `form:"page" binding:"required,min=1"` OrderBy string `form:"order_by" binding:"required,eq=created_at|eq=downloads|eq=views"` Order string `form:"order" binding:"required,eq=DESC|eq=ASC"` Keywords string `form:"keywords"` } // Get 获取给定用户的分享 func (service *ShareUserGetService) Get(c *gin.Context) serializer.Response { // 取得用户 userID, _ := c.Get("object_id") user, err := model.GetActiveUserByID(userID.(uint)) if err != nil || user.OptionsSerialized.ProfileOff { return serializer.Err(serializer.CodeNotFound, "", err) } // 列出分享 hotNum := model.GetIntSetting("hot_share_num", 10) if service.Type == "default" { hotNum = 10 } orderBy := "created_at desc" if service.Type == "hot" { orderBy = "views desc" } shares, total := model.ListShares(user.ID, int(service.Page), hotNum, orderBy, true) // 列出分享对应的文件 for i := 0; i < len(shares); i++ { shares[i].Source() } res := serializer.BuildShareList(shares, total) res.Data.(map[string]interface{})["user"] = struct { ID string `json:"id"` Nick string `json:"nick"` Group string `json:"group"` Date string `json:"date"` }{ hashid.HashID(user.ID, hashid.UserID), user.Nick, user.Group.Name, user.CreatedAt.Format("2006-01-02 15:04:05"), } return res } // Search 搜索公共分享 func (service *ShareListService) Search(c *gin.Context) serializer.Response { // 列出分享 shares, total := model.SearchShares(int(service.Page), 18, service.OrderBy+" "+ service.Order, service.Keywords) // 列出分享对应的文件 for i := 0; i < len(shares); i++ { shares[i].Source() } return serializer.BuildShareList(shares, total) } // List 列出用户分享 func (service *ShareListService) List(c *gin.Context, user *model.User) serializer.Response { // 列出分享 shares, total := model.ListShares(user.ID, int(service.Page), 18, service.OrderBy+" "+ service.Order, false) // 列出分享对应的文件 for i := 0; i < len(shares); i++ { shares[i].Source() } return serializer.BuildShareList(shares, total) } // Get 获取分享内容 func (service *ShareGetService) Get(c *gin.Context) serializer.Response { shareCtx, _ := c.Get("share") share := shareCtx.(*model.Share) // 是否已解锁 unlocked := true if share.Password != "" { sessionKey := fmt.Sprintf("share_unlock_%d", share.ID) unlocked = util.GetSession(c, sessionKey) != nil if !unlocked && service.Password != "" { // 如果未解锁,且指定了密码,则尝试解锁 if service.Password == share.Password { unlocked = true util.SetSession(c, map[string]interface{}{sessionKey: true}) } } } if unlocked { share.Viewed() } return serializer.Response{ Code: 0, Data: serializer.BuildShareResponse(share, unlocked), } } // CreateDownloadSession 创建下载会话 func (service *Service) CreateDownloadSession(c *gin.Context) serializer.Response { shareCtx, _ := c.Get("share") share := shareCtx.(*model.Share) userCtx, _ := c.Get("user") user := userCtx.(*model.User) // 创建文件系统 fs, err := filesystem.NewFileSystem(user) if err != nil { return serializer.DBErr("Failed to update share record", err) } defer fs.Recycle() // 重设文件系统处理目标为源文件 err = fs.SetTargetByInterface(share.Source()) if err != nil { return serializer.Err(serializer.CodeFileNotFound, "", err) } ctx := context.Background() // 重设根目录 if share.IsDir { fs.Root = &fs.DirTarget[0] // 找到目标文件 err = fs.ResetFileIfNotExist(ctx, service.Path) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } } // 取得下载地址 downloadURL, err := fs.GetDownloadURL(ctx, 0, "download_timeout") if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } return serializer.Response{ Code: 0, Data: downloadURL, } } // PreviewContent 预览文件,需要登录会话, isText - 是否为文本文件,文本文件会 // 强制经由服务端中转 func (service *Service) PreviewContent(ctx context.Context, c *gin.Context, isText bool) serializer.Response { shareCtx, _ := c.Get("share") share := shareCtx.(*model.Share) // 用于调下层service if share.IsDir { ctx = context.WithValue(ctx, fsctx.FolderModelCtx, share.Source()) ctx = context.WithValue(ctx, fsctx.PathCtx, service.Path) } else { ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source()) } subService := explorer.FileIDService{} return subService.PreviewContent(ctx, c, isText) } // CreateDocPreviewSession 创建Office预览会话,返回预览地址 func (service *Service) CreateDocPreviewSession(c *gin.Context) serializer.Response { shareCtx, _ := c.Get("share") share := shareCtx.(*model.Share) // 用于调下层service ctx := context.Background() if share.IsDir { ctx = context.WithValue(ctx, fsctx.FolderModelCtx, share.Source()) ctx = context.WithValue(ctx, fsctx.PathCtx, service.Path) } else { ctx = context.WithValue(ctx, fsctx.FileModelCtx, share.Source()) } subService := explorer.FileIDService{} return subService.CreateDocPreviewSession(ctx, c, false) } // List 列出分享的目录下的对象 func (service *Service) List(c *gin.Context) serializer.Response { shareCtx, _ := c.Get("share") share := shareCtx.(*model.Share) if !share.IsDir { return serializer.ParamErr("This is not a shared folder", nil) } if !path.IsAbs(service.Path) { return serializer.ParamErr("Invalid path", nil) } // 创建文件系统 fs, err := filesystem.NewFileSystem(share.Creator()) if err != nil { return serializer.Err(serializer.CodeCreateFSError, "", err) } defer fs.Recycle() // 上下文 ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 重设根目录 fs.Root = share.Source().(*model.Folder) fs.Root.Name = "/" // 分享Key上下文 ctx = context.WithValue(ctx, fsctx.ShareKeyCtx, hashid.HashID(share.ID, hashid.ShareID)) // 获取子项目 objects, err := fs.List(ctx, service.Path, nil) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) } return serializer.Response{ Code: 0, Data: serializer.BuildObjectList(0, objects, nil), } } // Thumb 获取被分享文件的缩略图 func (service *Service) Thumb(c *gin.Context) serializer.Response { shareCtx, _ := c.Get("share") share := shareCtx.(*model.Share) if !share.IsDir { return serializer.ParamErr("This share has no thumb", nil) } // 创建文件系统 fs, err := filesystem.NewFileSystem(share.Creator()) if err != nil { return serializer.Err(serializer.CodeCreateFSError, "", err) } defer fs.Recycle() // 重设根目录 fs.Root = share.Source().(*model.Folder) // 找到缩略图的父目录 exist, parent := fs.IsPathExist(service.Path) if !exist { return serializer.Err(serializer.CodeParentNotExist, "", nil) } ctx := context.WithValue(context.Background(), fsctx.LimitParentCtx, parent) // 获取文件ID fileID, err := hashid.DecodeHashID(c.Param("file"), hashid.FileID) if err != nil { return serializer.Err(serializer.CodeNotFound, "", err) } // 获取缩略图 resp, err := fs.GetThumb(ctx, uint(fileID)) if err != nil { return serializer.Err(serializer.CodeNotSet, "Failed to get thumb", err) } if resp.Redirect { c.Header("Cache-Control", fmt.Sprintf("max-age=%d", resp.MaxAge)) c.Redirect(http.StatusMovedPermanently, resp.URL) return serializer.Response{Code: -1} } defer resp.Content.Close() http.ServeContent(c.Writer, c.Request, "thumb.png", fs.FileTarget[0].UpdatedAt, resp.Content) return serializer.Response{Code: -1} } // Archive 创建批量下载归档 func (service *ArchiveService) Archive(c *gin.Context) serializer.Response { shareCtx, _ := c.Get("share") share := shareCtx.(*model.Share) userCtx, _ := c.Get("user") user := userCtx.(*model.User) // 是否有权限 if !user.Group.OptionsSerialized.ArchiveDownload { return serializer.Err(serializer.CodeGroupNotAllowed, "", nil) } if !share.IsDir { return serializer.ParamErr("This share cannot be batch downloaded", nil) } // 创建文件系统 fs, err := filesystem.NewFileSystem(user) if err != nil { return serializer.Err(serializer.CodeCreateFSError, "", err) } defer fs.Recycle() // 重设根目录 fs.Root = share.Source().(*model.Folder) // 找到要打包文件的父目录 exist, parent := fs.IsPathExist(service.Path) if !exist { return serializer.Err(serializer.CodeParentNotExist, "", nil) } // 限制操作范围为父目录下 ctx := context.WithValue(context.Background(), fsctx.LimitParentCtx, parent) // 用于调下层service tempUser := share.Creator() tempUser.Group.OptionsSerialized.ArchiveDownload = true c.Set("user", tempUser) subService := explorer.ItemIDService{ Dirs: service.Dirs, Items: service.Items, } return subService.Archive(ctx, c) } // SearchService 对分享的目录进行搜索 type SearchService struct { explorer.ItemSearchService } // Search 执行搜索 func (service *SearchService) Search(c *gin.Context) serializer.Response { shareCtx, _ := c.Get("share") share := shareCtx.(*model.Share) if !share.IsDir { return serializer.ParamErr("此分享无法列目录", nil) } if service.Path != "" && !path.IsAbs(service.Path) { return serializer.ParamErr("路径无效", nil) } // 创建文件系统 fs, err := filesystem.NewFileSystem(share.Creator()) if err != nil { return serializer.Err(serializer.CodeCreateFSError, "", err) } defer fs.Recycle() // 上下文 ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 重设根目录 fs.Root = share.Source().(*model.Folder) fs.Root.Name = "/" if service.Path != "" { ok, parent := fs.IsPathExist(service.Path) if !ok { return serializer.Err(serializer.CodeParentNotExist, "Cannot find parent folder", nil) } fs.Root = parent } // 分享Key上下文 ctx = context.WithValue(ctx, fsctx.ShareKeyCtx, hashid.HashID(share.ID, hashid.ShareID)) return service.SearchKeywords(c, fs, "%"+service.Keywords+"%") }