mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 20:26:36 +08:00
fix cloud library scale and playback compatibility
Root cause: display-only cloud library filtering was reused by scan jobs, merged cloud mounts could be skipped, and OpenList listing relied on WebDAV/first-page behavior that could cap huge directories around 100 items. Cloud scans also let new items consume probe budget before existing rows with missing track/artwork metadata.\n\nChanges:\n- split display filtering from scannable cloud filtering so merged cloud mounts still scan\n- add OpenList API pagination with WebDAV fallback\n- prioritize existing cloud media missing metadata before new imports\n- restrict automatic cloud sync to one successful 19:00-21:00 daily window and keep manual scan immediate\n- disable startup cloud full-scan by default, with an explicit opt-in setting\n- improve Emby/cloud playback compatibility and cache/search/test coverage from the continued work
This commit is contained in:
@@ -104,6 +104,30 @@ func embyPingHandler(_ *service.Container) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func embyRootHandler(svc *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, embyPublicSystemInfoPayload(c, svc))
|
||||
}
|
||||
}
|
||||
|
||||
func embyPublicSystemInfoPayload(c *gin.Context, svc *service.Container) map[string]any {
|
||||
if svc != nil && svc.Emby != nil {
|
||||
return embyWithRequestAddress(c, svc.Emby.SystemInfoPublic())
|
||||
}
|
||||
return embyWithRequestAddress(c, map[string]any{
|
||||
"Id": "mediastation-go-001",
|
||||
"ServerId": "mediastation-go-001",
|
||||
"ServerName": "MediaStationGo",
|
||||
"Version": "4.8.10.0",
|
||||
"ServerVersion": "4.8.10.0",
|
||||
"ProductName": "Emby Server",
|
||||
"OperatingSystem": "Windows",
|
||||
"SupportsHttps": false,
|
||||
"SupportsAutoDiscovery": true,
|
||||
"StartupWizardCompleted": true,
|
||||
})
|
||||
}
|
||||
|
||||
// ─── Users / Auth ────────────────────────────────────────────────────────────
|
||||
|
||||
type embyAuthByNameReq struct {
|
||||
@@ -446,6 +470,7 @@ func embyVirtualFoldersHandler(svc *service.Container) gin.HandlerFunc {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
libs = service.FilterDisplayCloudLibraries(c.Request.Context(), svc.Repo, libs)
|
||||
uid := embyUserID(c)
|
||||
visibility := service.UserDefaultMediaVisibility(c.Request.Context(), svc.Repo, uid)
|
||||
out := make([]gin.H, 0, len(libs))
|
||||
@@ -986,6 +1011,40 @@ func embyServerConfigurationHandler(_ *service.Container) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func embyPublicServerConfigurationHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"IsStartupWizardCompleted": true,
|
||||
"EnableRemoteAccess": true,
|
||||
"EnableUPnP": false,
|
||||
"EnableHttps": false,
|
||||
"RequireHttps": false,
|
||||
"LocalNetworkSubnets": []string{},
|
||||
"LocalNetworkAddresses": []string{},
|
||||
"RemoteClientBitrateLimit": 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func embyStartupConfigurationHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"IsStartupWizardCompleted": true,
|
||||
"StartupWizardCompleted": true,
|
||||
"EnableRemoteAccess": true,
|
||||
"UICulture": "zh-CN",
|
||||
"MetadataCountryCode": "CN",
|
||||
"PreferredMetadataLanguage": "zh-CN",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func embyQuickConnectEnabledHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, false)
|
||||
}
|
||||
}
|
||||
|
||||
func embyEmptyItemsHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"Items": []any{}, "TotalRecordCount": 0})
|
||||
@@ -1002,6 +1061,12 @@ func embyBrandingConfigHandler(_ *service.Container) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func embyBrandingCSSHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Data(http.StatusOK, "text/css; charset=utf-8", []byte(""))
|
||||
}
|
||||
}
|
||||
|
||||
func embyLocalizationOptionsHandler(_ *service.Container) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, []map[string]any{
|
||||
@@ -1022,6 +1087,13 @@ func registerEmbyRoutes(r *gin.Engine, jwtSecret string, svc *service.Container)
|
||||
c.Next()
|
||||
})
|
||||
|
||||
if prefix == "/emby" {
|
||||
grp.GET("", embyRootHandler(svc))
|
||||
grp.HEAD("", embyRootHandler(svc))
|
||||
grp.GET("/", embyRootHandler(svc))
|
||||
grp.HEAD("/", embyRootHandler(svc))
|
||||
}
|
||||
|
||||
// 公开端点
|
||||
for _, path := range []string{"/System/Info/Public", "/system/info/public"} {
|
||||
grp.GET(path, embySystemInfoPublicHandler(svc))
|
||||
@@ -1034,11 +1106,29 @@ func registerEmbyRoutes(r *gin.Engine, jwtSecret string, svc *service.Container)
|
||||
for _, path := range []string{"/System/Endpoint", "/system/endpoint"} {
|
||||
grp.GET(path, embySystemEndpointHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/System/Configuration/Public", "/system/configuration/public"} {
|
||||
grp.GET(path, embyPublicServerConfigurationHandler(svc))
|
||||
grp.HEAD(path, embyPublicServerConfigurationHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/Startup/Configuration", "/startup/configuration"} {
|
||||
grp.GET(path, embyStartupConfigurationHandler(svc))
|
||||
grp.HEAD(path, embyStartupConfigurationHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/Startup/Complete", "/startup/complete"} {
|
||||
grp.POST(path, embyNoContentHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/QuickConnect/Enabled", "/quickconnect/enabled"} {
|
||||
grp.GET(path, embyQuickConnectEnabledHandler(svc))
|
||||
grp.HEAD(path, embyQuickConnectEnabledHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/System/Ping", "/system/ping"} {
|
||||
grp.GET(path, embyPingHandler(svc))
|
||||
grp.HEAD(path, embyPingHandler(svc))
|
||||
grp.POST(path, embyPingHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/Sessions/Capabilities", "/Sessions/Capabilities/Full", "/sessions/capabilities", "/sessions/capabilities/full"} {
|
||||
grp.POST(path, embyNoContentHandler(svc))
|
||||
}
|
||||
// 30/min per IP: many Emby clients sit behind a single NAT/reverse-proxy
|
||||
// IP, so a low limit would throttle legitimate logins into 429s.
|
||||
embyLoginLimiter := middleware.NewRateLimiter(30, 1*time.Minute)
|
||||
@@ -1051,6 +1141,10 @@ func registerEmbyRoutes(r *gin.Engine, jwtSecret string, svc *service.Container)
|
||||
for _, path := range []string{"/Branding/Configuration", "/branding/configuration"} {
|
||||
grp.GET(path, embyBrandingConfigHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/Branding/Css", "/branding/css"} {
|
||||
grp.GET(path, embyBrandingCSSHandler(svc))
|
||||
grp.HEAD(path, embyBrandingCSSHandler(svc))
|
||||
}
|
||||
for _, path := range []string{"/Localization/Options", "/localization/options"} {
|
||||
grp.GET(path, embyLocalizationOptionsHandler(svc))
|
||||
}
|
||||
@@ -1109,8 +1203,6 @@ func registerEmbyRoutes(r *gin.Engine, jwtSecret string, svc *service.Container)
|
||||
auth.POST("/Sessions/Playing", embyPlayingProgressHandler(svc))
|
||||
auth.POST("/Sessions/Playing/Progress", embyPlayingProgressHandler(svc))
|
||||
auth.POST("/Sessions/Playing/Stopped", embyPlayingProgressHandler(svc))
|
||||
auth.POST("/Sessions/Capabilities", embyNoContentHandler(svc))
|
||||
auth.POST("/Sessions/Capabilities/Full", embyNoContentHandler(svc))
|
||||
|
||||
auth.POST("/Users/:userId/FavoriteItems/:itemId", embyFavoriteHandler(svc, true))
|
||||
auth.DELETE("/Users/:userId/FavoriteItems/:itemId", embyFavoriteHandler(svc, false))
|
||||
@@ -1175,8 +1267,6 @@ func registerLowercaseEmbyAuthRoutes(auth *gin.RouterGroup, svc *service.Contain
|
||||
auth.POST("/sessions/playing", embyPlayingProgressHandler(svc))
|
||||
auth.POST("/sessions/playing/progress", embyPlayingProgressHandler(svc))
|
||||
auth.POST("/sessions/playing/stopped", embyPlayingProgressHandler(svc))
|
||||
auth.POST("/sessions/capabilities", embyNoContentHandler(svc))
|
||||
auth.POST("/sessions/capabilities/full", embyNoContentHandler(svc))
|
||||
|
||||
auth.POST("/users/:userId/favoriteitems/:itemId", embyFavoriteHandler(svc, true))
|
||||
auth.DELETE("/users/:userId/favoriteitems/:itemId", embyFavoriteHandler(svc, false))
|
||||
|
||||
Reference in New Issue
Block a user