feat(wopi): change doc preview config based on WOPI discovery results
This commit is contained in:
parent
c39daeb0d0
commit
4541400755
7 changed files with 107 additions and 22 deletions
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/email"
|
"github.com/cloudreve/Cloudreve/v3/pkg/email"
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/mq"
|
"github.com/cloudreve/Cloudreve/v3/pkg/mq"
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/task"
|
"github.com/cloudreve/Cloudreve/v3/pkg/task"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
)
|
)
|
||||||
|
@ -95,6 +96,12 @@ func Init(path string, statics fs.FS) {
|
||||||
auth.Init()
|
auth.Init()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"master",
|
||||||
|
func() {
|
||||||
|
wopi.Init()
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dependency := range dependencies {
|
for _, dependency := range dependencies {
|
||||||
|
|
|
@ -115,4 +115,8 @@ Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; verti
|
||||||
{Name: "office_preview_service", Value: "https://view.officeapps.live.com/op/view.aspx?src={$src}", Type: "preview"},
|
{Name: "office_preview_service", Value: "https://view.officeapps.live.com/op/view.aspx?src={$src}", Type: "preview"},
|
||||||
{Name: "show_app_promotion", Value: "1", Type: "mobile"},
|
{Name: "show_app_promotion", Value: "1", Type: "mobile"},
|
||||||
{Name: "public_resource_maxage", Value: "86400", Type: "timeout"},
|
{Name: "public_resource_maxage", Value: "86400", Type: "timeout"},
|
||||||
|
{Name: "wopi_enabled", Value: "0", Type: "wopi"},
|
||||||
|
{Name: "wopi_endpoint", Value: "", Type: "wopi"},
|
||||||
|
{Name: "wopi_max_size", Value: "52428800", Type: "wopi"},
|
||||||
|
{Name: "wopi_session_timeout", Value: "43200", Type: "wopi"},
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,22 +7,23 @@ import (
|
||||||
|
|
||||||
// SiteConfig 站点全局设置序列
|
// SiteConfig 站点全局设置序列
|
||||||
type SiteConfig struct {
|
type SiteConfig struct {
|
||||||
SiteName string `json:"title"`
|
SiteName string `json:"title"`
|
||||||
LoginCaptcha bool `json:"loginCaptcha"`
|
LoginCaptcha bool `json:"loginCaptcha"`
|
||||||
RegCaptcha bool `json:"regCaptcha"`
|
RegCaptcha bool `json:"regCaptcha"`
|
||||||
ForgetCaptcha bool `json:"forgetCaptcha"`
|
ForgetCaptcha bool `json:"forgetCaptcha"`
|
||||||
EmailActive bool `json:"emailActive"`
|
EmailActive bool `json:"emailActive"`
|
||||||
Themes string `json:"themes"`
|
Themes string `json:"themes"`
|
||||||
DefaultTheme string `json:"defaultTheme"`
|
DefaultTheme string `json:"defaultTheme"`
|
||||||
HomepageViewMethod string `json:"home_view_method"`
|
HomepageViewMethod string `json:"home_view_method"`
|
||||||
ShareViewMethod string `json:"share_view_method"`
|
ShareViewMethod string `json:"share_view_method"`
|
||||||
Authn bool `json:"authn"`
|
Authn bool `json:"authn"`
|
||||||
User User `json:"user"`
|
User User `json:"user"`
|
||||||
ReCaptchaKey string `json:"captcha_ReCaptchaKey"`
|
ReCaptchaKey string `json:"captcha_ReCaptchaKey"`
|
||||||
CaptchaType string `json:"captcha_type"`
|
CaptchaType string `json:"captcha_type"`
|
||||||
TCaptchaCaptchaAppId string `json:"tcaptcha_captcha_app_id"`
|
TCaptchaCaptchaAppId string `json:"tcaptcha_captcha_app_id"`
|
||||||
RegisterEnabled bool `json:"registerEnabled"`
|
RegisterEnabled bool `json:"registerEnabled"`
|
||||||
AppPromotion bool `json:"app_promotion"`
|
AppPromotion bool `json:"app_promotion"`
|
||||||
|
WopiExts []string `json:"wopi_exts"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type task struct {
|
type task struct {
|
||||||
|
@ -60,7 +61,7 @@ func checkSettingValue(setting map[string]string, key string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// BuildSiteConfig 站点全局设置
|
// BuildSiteConfig 站点全局设置
|
||||||
func BuildSiteConfig(settings map[string]string, user *model.User) Response {
|
func BuildSiteConfig(settings map[string]string, user *model.User, wopiExts []string) Response {
|
||||||
var userRes User
|
var userRes User
|
||||||
if user != nil {
|
if user != nil {
|
||||||
userRes = BuildUser(*user)
|
userRes = BuildUser(*user)
|
||||||
|
@ -85,6 +86,7 @@ func BuildSiteConfig(settings map[string]string, user *model.User) Response {
|
||||||
TCaptchaCaptchaAppId: checkSettingValue(settings, "captcha_TCaptcha_CaptchaAppId"),
|
TCaptchaCaptchaAppId: checkSettingValue(settings, "captcha_TCaptcha_CaptchaAppId"),
|
||||||
RegisterEnabled: model.IsTrueVal(checkSettingValue(settings, "register_enabled")),
|
RegisterEnabled: model.IsTrueVal(checkSettingValue(settings, "register_enabled")),
|
||||||
AppPromotion: model.IsTrueVal(checkSettingValue(settings, "show_app_promotion")),
|
AppPromotion: model.IsTrueVal(checkSettingValue(settings, "show_app_promotion")),
|
||||||
|
WopiExts: wopiExts,
|
||||||
}}
|
}}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@ func TestCheckSettingValue(t *testing.T) {
|
||||||
func TestBuildSiteConfig(t *testing.T) {
|
func TestBuildSiteConfig(t *testing.T) {
|
||||||
asserts := assert.New(t)
|
asserts := assert.New(t)
|
||||||
|
|
||||||
res := BuildSiteConfig(map[string]string{"not exist": ""}, &model.User{})
|
res := BuildSiteConfig(map[string]string{"not exist": ""}, &model.User{}, nil)
|
||||||
asserts.Equal("", res.Data.(SiteConfig).SiteName)
|
asserts.Equal("", res.Data.(SiteConfig).SiteName)
|
||||||
|
|
||||||
res = BuildSiteConfig(map[string]string{"siteName": "123"}, &model.User{})
|
res = BuildSiteConfig(map[string]string{"siteName": "123"}, &model.User{}, nil)
|
||||||
asserts.Equal("123", res.Data.(SiteConfig).SiteName)
|
asserts.Equal("123", res.Data.(SiteConfig).SiteName)
|
||||||
|
|
||||||
// 非空用户
|
// 非空用户
|
||||||
|
@ -29,7 +29,7 @@ func TestBuildSiteConfig(t *testing.T) {
|
||||||
Model: gorm.Model{
|
Model: gorm.Model{
|
||||||
ID: 5,
|
ID: 5,
|
||||||
},
|
},
|
||||||
})
|
}, nil)
|
||||||
asserts.Len(res.Data.(SiteConfig).User.ID, 4)
|
asserts.Len(res.Data.(SiteConfig).User.ID, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,9 @@ import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ActonType string
|
type ActonType string
|
||||||
|
@ -19,6 +21,27 @@ const (
|
||||||
DiscoverRefreshDuration = 24 * 3600 // 24 hrs
|
DiscoverRefreshDuration = 24 * 3600 // 24 hrs
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (c *client) AvailableExts() []string {
|
||||||
|
if err := c.checkDiscovery(); err != nil {
|
||||||
|
util.Log().Error("Failed to check WOPI discovery: %s", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
c.mu.RUnlock()
|
||||||
|
defer c.mu.RUnlock()
|
||||||
|
exts := make([]string, 0, len(c.actions))
|
||||||
|
for ext, actions := range c.actions {
|
||||||
|
_, previewable := actions[string(ActionPreview)]
|
||||||
|
_, editable := actions[string(ActionEdit)]
|
||||||
|
|
||||||
|
if previewable || editable {
|
||||||
|
exts = append(exts, strings.TrimPrefix(ext, "."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return exts
|
||||||
|
}
|
||||||
|
|
||||||
// checkDiscovery checks if discovery content is needed to be refreshed.
|
// checkDiscovery checks if discovery content is needed to be refreshed.
|
||||||
// If so, it will refresh discovery content.
|
// If so, it will refresh discovery content.
|
||||||
func (c *client) checkDiscovery() error {
|
func (c *client) checkDiscovery() error {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
model "github.com/cloudreve/Cloudreve/v3/models"
|
model "github.com/cloudreve/Cloudreve/v3/models"
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
"github.com/cloudreve/Cloudreve/v3/pkg/cache"
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/request"
|
"github.com/cloudreve/Cloudreve/v3/pkg/request"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -13,11 +14,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client interface {
|
type Client interface {
|
||||||
|
// NewSession creates a new document session with access token.
|
||||||
|
NewSession(user *model.User, file *model.File, action ActonType) (*Session, error)
|
||||||
|
// AvailableExts returns a list of file extensions that are supported by WOPI.
|
||||||
|
AvailableExts() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrActionNotSupported = errors.New("action not supported by current wopi endpoint")
|
ErrActionNotSupported = errors.New("action not supported by current wopi endpoint")
|
||||||
|
|
||||||
|
Default Client
|
||||||
|
DefaultMu sync.Mutex
|
||||||
|
|
||||||
queryPlaceholders = map[string]string{
|
queryPlaceholders = map[string]string{
|
||||||
"BUSINESS_USER": "",
|
"BUSINESS_USER": "",
|
||||||
"DC_LLCC": "lng",
|
"DC_LLCC": "lng",
|
||||||
|
@ -38,6 +46,24 @@ const (
|
||||||
wopiSrcPlaceholder = "WOPI_SOURCE"
|
wopiSrcPlaceholder = "WOPI_SOURCE"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Init initializes a new global WOPI client.
|
||||||
|
func Init() {
|
||||||
|
settings := model.GetSettingByNames("wopi_endpoint", "wopi_enabled")
|
||||||
|
if !model.IsTrueVal(settings["wopi_enabled"]) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wopiClient, err := NewClient(settings["wopi_endpoint"], cache.Store, request.NewClient())
|
||||||
|
if err != nil {
|
||||||
|
util.Log().Error("Failed to initialize WOPI client: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
DefaultMu.Lock()
|
||||||
|
Default = wopiClient
|
||||||
|
DefaultMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
type client struct {
|
type client struct {
|
||||||
cache cache.Driver
|
cache cache.Driver
|
||||||
http request.Client
|
http request.Client
|
||||||
|
@ -53,6 +79,21 @@ type config struct {
|
||||||
discoveryEndpoint *url.URL
|
discoveryEndpoint *url.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewClient(endpoint string, cache cache.Driver, http request.Client) (Client, error) {
|
||||||
|
endpointUrl, err := url.Parse(endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse WOPI endpoint: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &client{
|
||||||
|
cache: cache,
|
||||||
|
http: http,
|
||||||
|
config: config{
|
||||||
|
discoveryEndpoint: endpointUrl,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *client) NewSession(user *model.User, file *model.File, action ActonType) (*Session, error) {
|
func (c *client) NewSession(user *model.User, file *model.File, action ActonType) (*Session, error) {
|
||||||
if err := c.checkDiscovery(); err != nil {
|
if err := c.checkDiscovery(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -79,6 +120,8 @@ func (c *client) NewSession(user *model.User, file *model.File, action ActonType
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Replace query parameters in action URL template. Some placeholders need to be replaced
|
||||||
|
// at the frontend, e.g. `THEME_ID`.
|
||||||
func generateActionUrl(src string, fileSrc string) (*url.URL, error) {
|
func generateActionUrl(src string, fileSrc string) (*url.URL, error) {
|
||||||
src = strings.ReplaceAll(src, "<", "")
|
src = strings.ReplaceAll(src, "<", "")
|
||||||
src = strings.ReplaceAll(src, ">", "")
|
src = strings.ReplaceAll(src, ">", "")
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
|
"github.com/cloudreve/Cloudreve/v3/pkg/conf"
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
|
||||||
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
"github.com/cloudreve/Cloudreve/v3/pkg/util"
|
||||||
|
"github.com/cloudreve/Cloudreve/v3/pkg/wopi"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/mojocn/base64Captcha"
|
"github.com/mojocn/base64Captcha"
|
||||||
)
|
)
|
||||||
|
@ -30,14 +31,19 @@ func SiteConfig(c *gin.Context) {
|
||||||
"show_app_promotion",
|
"show_app_promotion",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var wopiExts []string
|
||||||
|
if wopi.Default != nil {
|
||||||
|
wopiExts = wopi.Default.AvailableExts()
|
||||||
|
}
|
||||||
|
|
||||||
// 如果已登录,则同时返回用户信息和标签
|
// 如果已登录,则同时返回用户信息和标签
|
||||||
user, _ := c.Get("user")
|
user, _ := c.Get("user")
|
||||||
if user, ok := user.(*model.User); ok {
|
if user, ok := user.(*model.User); ok {
|
||||||
c.JSON(200, serializer.BuildSiteConfig(siteConfig, user))
|
c.JSON(200, serializer.BuildSiteConfig(siteConfig, user, wopiExts))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, serializer.BuildSiteConfig(siteConfig, nil))
|
c.JSON(200, serializer.BuildSiteConfig(siteConfig, nil, wopiExts))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ping 状态检查页面
|
// Ping 状态检查页面
|
||||||
|
|
Loading…
Add table
Reference in a new issue