feat(wopi): change doc preview config based on WOPI discovery results

pull/1626/head
HFO4 2 years ago
parent c39daeb0d0
commit 4541400755

@ -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…
Cancel
Save