package model
import (
"errors"
"fmt"
"strings"
"time"
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
"github.com/cloudreve/Cloudreve/v3/pkg/hashid"
"github.com/cloudreve/Cloudreve/v3/pkg/util"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
)
// Share 分享模型
type Share struct {
gorm . Model
Password string // 分享密码,空值为非加密分享
IsDir bool // 原始资源是否为目录
UserID uint // 创建用户ID
SourceID uint // 原始资源ID
Views int // 浏览数
Downloads int // 下载数
RemainDownloads int // 剩余下载配额,负值标识无限制
Expires * time . Time // 过期时间,空值表示无过期时间
PreviewEnabled bool // 是否允许直接预览
SourceName string ` gorm:"index:source" ` // 用于搜索的字段
// 数据库忽略字段
User User ` gorm:"PRELOAD:false,association_autoupdate:false" `
File File ` gorm:"PRELOAD:false,association_autoupdate:false" `
Folder Folder ` gorm:"PRELOAD:false,association_autoupdate:false" `
}
// Create 创建分享
func ( share * Share ) Create ( ) ( uint , error ) {
if err := DB . Create ( share ) . Error ; err != nil {
util . Log ( ) . Warning ( "Failed to insert share record: %s" , err )
return 0 , err
}
return share . ID , nil
}
// GetShareByHashID 根据HashID查找分享
func GetShareByHashID ( hashID string ) * Share {
id , err := hashid . DecodeHashID ( hashID , hashid . ShareID )
if err != nil {
return nil
}
var share Share
result := DB . First ( & share , id )
if result . Error != nil {
return nil
}
return & share
}
// IsAvailable 返回此分享是否可用(是否过期)
func ( share * Share ) IsAvailable ( ) bool {
if share . RemainDownloads == 0 {
return false
}
if share . Expires != nil && time . Now ( ) . After ( * share . Expires ) {
return false
}
// 检查创建者状态
if share . Creator ( ) . Status != Active {
return false
}
// 检查源对象是否存在
var sourceID uint
if share . IsDir {
folder := share . SourceFolder ( )
sourceID = folder . ID
} else {
file := share . SourceFile ( )
sourceID = file . ID
}
if sourceID == 0 {
// TODO 是否要在这里删除这个无效分享?
return false
}
return true
}
// Creator 获取分享的创建者
func ( share * Share ) Creator ( ) * User {
if share . User . ID == 0 {
share . User , _ = GetUserByID ( share . UserID )
}
return & share . User
}
// Source 返回源对象
func ( share * Share ) Source ( ) interface { } {
if share . IsDir {
return share . SourceFolder ( )
}
return share . SourceFile ( )
}
// SourceFolder 获取源目录
func ( share * Share ) SourceFolder ( ) * Folder {
if share . Folder . ID == 0 {
folders , _ := GetFoldersByIDs ( [ ] uint { share . SourceID } , share . UserID )
if len ( folders ) > 0 {
share . Folder = folders [ 0 ]
}
}
return & share . Folder
}
// SourceFile 获取源文件
func ( share * Share ) SourceFile ( ) * File {
if share . File . ID == 0 {
files , _ := GetFilesByIDs ( [ ] uint { share . SourceID } , share . UserID )
if len ( files ) > 0 {
share . File = files [ 0 ]
}
}
return & share . File
}
// CanBeDownloadBy 返回此分享是否可以被给定用户下载
func ( share * Share ) CanBeDownloadBy ( user * User ) error {
// 用户组权限
if ! user . Group . OptionsSerialized . ShareDownload {
if user . IsAnonymous ( ) {
return errors . New ( "you must login to download" )
}
return errors . New ( "your group has no permission to download" )
}
return nil
}
// WasDownloadedBy 返回分享是否已被用户下载过
func ( share * Share ) WasDownloadedBy ( user * User , c * gin . Context ) ( exist bool ) {
if user . IsAnonymous ( ) {
exist = util . GetSession ( c , fmt . Sprintf ( "share_%d_%d" , share . ID , user . ID ) ) != nil
} else {
_ , exist = cache . Get ( fmt . Sprintf ( "share_%d_%d" , share . ID , user . ID ) )
}
return exist
}
// DownloadBy 增加下载次数,匿名用户不会缓存
func ( share * Share ) DownloadBy ( user * User , c * gin . Context ) error {
if ! share . WasDownloadedBy ( user , c ) {
share . Downloaded ( )
if ! user . IsAnonymous ( ) {
cache . Set ( fmt . Sprintf ( "share_%d_%d" , share . ID , user . ID ) , true ,
GetIntSetting ( "share_download_session_timeout" , 2073600 ) )
} else {
util . SetSession ( c , map [ string ] interface { } { fmt . Sprintf ( "share_%d_%d" , share . ID , user . ID ) : true } )
}
}
return nil
}
// Viewed 增加访问次数
func ( share * Share ) Viewed ( ) {
share . Views ++
DB . Model ( share ) . UpdateColumn ( "views" , gorm . Expr ( "views + ?" , 1 ) )
}
// Downloaded 增加下载次数
func ( share * Share ) Downloaded ( ) {
share . Downloads ++
if share . RemainDownloads > 0 {
share . RemainDownloads --
}
DB . Model ( share ) . Updates ( map [ string ] interface { } {
"downloads" : share . Downloads ,
"remain_downloads" : share . RemainDownloads ,
} )
}
// Update 更新分享属性
func ( share * Share ) Update ( props map [ string ] interface { } ) error {
return DB . Model ( share ) . Updates ( props ) . Error
}
// Delete 删除分享
func ( share * Share ) Delete ( ) error {
return DB . Model ( share ) . Delete ( share ) . Error
}
// DeleteShareBySourceIDs 根据原始资源类型和ID删除文件
func DeleteShareBySourceIDs ( sources [ ] uint , isDir bool ) error {
return DB . Where ( "source_id in (?) and is_dir = ?" , sources , isDir ) . Delete ( & Share { } ) . Error
}
// ListShares 列出UID下的分享
func ListShares ( uid uint , page , pageSize int , order string , publicOnly bool ) ( [ ] Share , int ) {
var (
shares [ ] Share
total int
)
dbChain := DB
dbChain = dbChain . Where ( "user_id = ?" , uid )
if publicOnly {
dbChain = dbChain . Where ( "password = ?" , "" )
}
// 计算总数用于分页
dbChain . Model ( & Share { } ) . Count ( & total )
// 查询记录
dbChain . Limit ( pageSize ) . Offset ( ( page - 1 ) * pageSize ) . Order ( order ) . Find ( & shares )
return shares , total
}
// SearchShares 根据关键字搜索分享
func SearchShares ( page , pageSize int , order , keywords string ) ( [ ] Share , int ) {
var (
shares [ ] Share
total int
)
keywordList := strings . Split ( keywords , " " )
availableList := make ( [ ] string , 0 , len ( keywordList ) )
for i := 0 ; i < len ( keywordList ) ; i ++ {
if len ( keywordList [ i ] ) > 0 {
availableList = append ( availableList , keywordList [ i ] )
}
}
if len ( availableList ) == 0 {
return shares , 0
}
dbChain := DB
dbChain = dbChain . Where ( "password = ? and remain_downloads <> 0 and (expires is NULL or expires > ?) and source_name like ?" , "" , time . Now ( ) , "%" + strings . Join ( availableList , "%" ) + "%" )
// 计算总数用于分页
dbChain . Model ( & Share { } ) . Count ( & total )
// 查询记录
dbChain . Limit ( pageSize ) . Offset ( ( page - 1 ) * pageSize ) . Order ( order ) . Find ( & shares )
return shares , total
}