mirror of
https://github.com/truewhile/MeBox.git
synced 2026-10-03 20:26:36 +08:00
feat: Emby 挂载独立管理(媒体库级选择挂载 + 每库代理开关)
- 新增 EmbyMount 表与 /admin/emby/mounts CRUD:把远程 Emby 的媒体库按需
挂载到本项目(同一服务器可选择性挂载/全量挂载),代理开关下沉到每个
挂载(一个 Emby 服务器可部分库代理、部分库直连)
- 伪装 ID 改为 embyremote~{mountID}~{remoteID}:播放/详情/状态/图片链路
全部经 ResolveMount 解析挂载与账号
- 新增「Emby 挂载」独立菜单页(/emby-mount):账号管理(添加/测试/编辑/
删除)+ 挂载列表(代理切换/停用/取消)+ 选择挂载对话框(多选+全量)
- STRM 管理页过滤 emby_remote 账号;账号表单移除 emby 类型
- Boot 幂等 AutoSeedMounts:旧 emby 账号自动全量挂载(沿用旧代理配置),
升级后媒体库不消失
- 修复:远程流请求注入浏览器 UA(防 Cloudflare 风控)+ 代理强制 Static=true
(阻止远程 ffmpeg 转码调度,反代纯字节中继)
- 全局搜索改为按挂载逐个搜索(结果归属与伪装 ID 正确)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/ShukeBta/MMTL/internal/model"
|
||||
)
|
||||
|
||||
// EmbyMountRepository 持久化远程 Emby 媒体库挂载。
|
||||
type EmbyMountRepository struct{ db *gorm.DB }
|
||||
|
||||
func (r *EmbyMountRepository) Create(ctx context.Context, m *model.EmbyMount) error {
|
||||
return withSQLiteBusyRetry(ctx, func() error {
|
||||
return r.db.WithContext(ctx).Create(m).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *EmbyMountRepository) CreateInBatches(ctx context.Context, mounts []*model.EmbyMount, batchSize int) error {
|
||||
if len(mounts) == 0 {
|
||||
return nil
|
||||
}
|
||||
if batchSize <= 0 {
|
||||
batchSize = 50
|
||||
}
|
||||
return withSQLiteBusyRetry(ctx, func() error {
|
||||
return r.db.WithContext(ctx).CreateInBatches(mounts, batchSize).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *EmbyMountRepository) FindByID(ctx context.Context, id string) (*model.EmbyMount, error) {
|
||||
var m model.EmbyMount
|
||||
err := r.db.WithContext(ctx).Where("id = ?", id).First(&m).Error
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &m, nil
|
||||
}
|
||||
|
||||
func (r *EmbyMountRepository) List(ctx context.Context) ([]model.EmbyMount, error) {
|
||||
var rows []model.EmbyMount
|
||||
err := r.db.WithContext(ctx).Order("created_at desc").Find(&rows).Error
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (r *EmbyMountRepository) ListByAccountID(ctx context.Context, accountID string) ([]model.EmbyMount, error) {
|
||||
var rows []model.EmbyMount
|
||||
err := r.db.WithContext(ctx).Where("account_id = ?", accountID).Order("created_at asc").Find(&rows).Error
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func (r *EmbyMountRepository) CountByAccountID(ctx context.Context, accountID string) (int64, error) {
|
||||
var count int64
|
||||
err := r.db.WithContext(ctx).Model(&model.EmbyMount{}).Where("account_id = ?", accountID).Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (r *EmbyMountRepository) Update(ctx context.Context, m *model.EmbyMount) error {
|
||||
return withSQLiteBusyRetry(ctx, func() error {
|
||||
return r.db.WithContext(ctx).Model(&model.EmbyMount{}).Where("id = ?", m.ID).Updates(map[string]any{
|
||||
"name": m.Name,
|
||||
"proxy_play": m.ProxyPlay,
|
||||
"enabled": m.Enabled,
|
||||
"remote_view_id": m.RemoteViewID,
|
||||
"remote_view_name": m.RemoteViewName,
|
||||
"collection_type": m.CollectionType,
|
||||
"updated_at": time.Now(),
|
||||
}).Error
|
||||
})
|
||||
}
|
||||
|
||||
func (r *EmbyMountRepository) Delete(ctx context.Context, id string) error {
|
||||
return withSQLiteBusyRetry(ctx, func() error {
|
||||
return r.db.WithContext(ctx).Where("id = ?", id).Delete(&model.EmbyMount{}).Error
|
||||
})
|
||||
}
|
||||
|
||||
// DeleteByAccountID 删除账号下全部挂载(删除账号时级联清理)。
|
||||
func (r *EmbyMountRepository) DeleteByAccountID(ctx context.Context, accountID string) (int64, error) {
|
||||
var count int64
|
||||
err := withSQLiteBusyRetry(ctx, func() error {
|
||||
res := r.db.WithContext(ctx).Where("account_id = ?", accountID).Delete(&model.EmbyMount{})
|
||||
count = res.RowsAffected
|
||||
return res.Error
|
||||
})
|
||||
return count, err
|
||||
}
|
||||
@@ -33,6 +33,7 @@ type Container struct {
|
||||
StrmUpload *StrmUploadTaskRepository
|
||||
StrmDirCache *StrmDirCacheRepository
|
||||
ScrapeTask *ScrapeTaskRepository
|
||||
EmbyMount *EmbyMountRepository
|
||||
}
|
||||
|
||||
// New 将每个 repository 连接到单个 *gorm.DB。
|
||||
@@ -62,5 +63,6 @@ func New(db *gorm.DB) *Container {
|
||||
StrmUpload: &StrmUploadTaskRepository{db: db},
|
||||
StrmDirCache: &StrmDirCacheRepository{db: db},
|
||||
ScrapeTask: &ScrapeTaskRepository{db: db},
|
||||
EmbyMount: &EmbyMountRepository{db: db},
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user