Skip to content

Commit 2182ccf

Browse files
committed
feat(media): 清空刮削、删除已失效
1 parent b5f72ec commit 2182ccf

3 files changed

Lines changed: 123 additions & 0 deletions

File tree

internal/db/media.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,55 @@ func ClearMediaItemsByScanPath(scanPathID uint) error {
235235
return db.Unscoped().Where("scan_path_id = ?", scanPathID).Delete(&model.MediaItem{}).Error
236236
}
237237

238+
// ClearMediaScrapedData 清空指定类型的所有刮削数据(保留扫描出的文件记录本身)
239+
// 仅清空刮削结果相关字段,不删除条目,便于重新刮削。
240+
// mediaType 为空时表示对所有类型生效。
241+
func ClearMediaScrapedData(mediaType model.MediaType) (int64, error) {
242+
tx := db.Unscoped().Model(&model.MediaItem{})
243+
if mediaType != "" {
244+
tx = tx.Where("media_type = ?", mediaType)
245+
}
246+
updates := map[string]interface{}{
247+
"scraped_name": "",
248+
"description": "",
249+
"cover": "",
250+
"release_date": "",
251+
"rating": 0,
252+
"genre": "",
253+
"authors": "",
254+
"plot": "",
255+
"reviews": "",
256+
"external_id": "",
257+
"album_artist": "",
258+
"publisher": "",
259+
"isbn": "",
260+
"scraped_at": nil,
261+
}
262+
result := tx.Updates(updates)
263+
return result.RowsAffected, result.Error
264+
}
265+
266+
// ListAllValidMediaItemsForCheck 列出指定类型下所有未删除的媒体条目(仅取必要字段,用于失效检查)
267+
// mediaType 为空时返回所有类型条目。
268+
func ListAllValidMediaItemsForCheck(mediaType model.MediaType) ([]model.MediaItem, error) {
269+
var items []model.MediaItem
270+
tx := db.Model(&model.MediaItem{}).
271+
Select("id, media_type, scan_path_id, file_name, folder_path, is_folder")
272+
if mediaType != "" {
273+
tx = tx.Where("media_type = ?", mediaType)
274+
}
275+
err := tx.Find(&items).Error
276+
return items, err
277+
}
278+
279+
// DeleteMediaItemsByIDs 按ID列表硬删除媒体条目
280+
func DeleteMediaItemsByIDs(ids []uint) error {
281+
if len(ids) == 0 {
282+
return nil
283+
}
284+
return db.Unscoped().Where("id IN ?", ids).Delete(&model.MediaItem{}).Error
285+
}
286+
238287
// ListAlbums 列出所有专辑(音乐专用)
239288
func ListAlbums(q MediaItemQuery) ([]AlbumInfo, int64, error) {
240289
type albumRow struct {

server/handles/media.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ import (
88
"sync"
99
"time"
1010

11+
stdpath "path"
12+
1113
"github.com/OpenListTeam/OpenList/v4/internal/conf"
1214
"github.com/OpenListTeam/OpenList/v4/internal/db"
15+
"github.com/OpenListTeam/OpenList/v4/internal/fs"
1316
"github.com/OpenListTeam/OpenList/v4/internal/media"
1417
"github.com/OpenListTeam/OpenList/v4/internal/media/scraper"
1518
"github.com/OpenListTeam/OpenList/v4/internal/model"
@@ -306,6 +309,75 @@ func ClearMediaDB(c *gin.Context) {
306309
common.SuccessResp(c)
307310
}
308311

312+
// ClearMediaScrape 清空刮削数据(保留扫描记录,但清空所有刮削结果字段)
313+
// 参数:media_type 可选,留空表示所有类型;item_id 暂不支持(统一为类型/全部)
314+
func ClearMediaScrape(c *gin.Context) {
315+
mediaType := model.MediaType(c.Query("media_type"))
316+
affected, err := db.ClearMediaScrapedData(mediaType)
317+
if err != nil {
318+
common.ErrorResp(c, err, 500)
319+
return
320+
}
321+
// 同步清掉对应媒体配置中的最近刮削时间
322+
if mediaType != "" {
323+
if cfg, e := db.GetMediaConfig(mediaType); e == nil && cfg != nil {
324+
cfg.LastScrapeAt = nil
325+
_ = db.SaveMediaConfig(cfg)
326+
}
327+
} else {
328+
if cfgs, e := db.GetAllMediaConfigs(); e == nil {
329+
for i := range cfgs {
330+
cfgs[i].LastScrapeAt = nil
331+
_ = db.SaveMediaConfig(&cfgs[i])
332+
}
333+
}
334+
}
335+
common.SuccessResp(c, gin.H{"affected": affected})
336+
}
337+
338+
// DeleteInvalidMedia 删除已失效的媒体条目(即对应文件 / 文件夹在存储中已不存在)
339+
// 参数:media_type 可选,留空表示扫描所有类型
340+
// 返回:检测总数、被删除条目数
341+
func DeleteInvalidMedia(c *gin.Context) {
342+
mediaType := model.MediaType(c.Query("media_type"))
343+
344+
items, err := db.ListAllValidMediaItemsForCheck(mediaType)
345+
if err != nil {
346+
common.ErrorResp(c, err, 500)
347+
return
348+
}
349+
350+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
351+
defer cancel()
352+
353+
var invalidIDs []uint
354+
for i := range items {
355+
it := &items[i]
356+
// 拼接文件 / 文件夹的完整 VFS 路径
357+
// - 文件夹模式 (is_folder=true): folder_path 即文件夹自身(扫描根 + 文件夹名 视情况而定);这里直接使用 folder_path/file_name
358+
// - 普通文件: folder_path 是所在目录,file_name 是文件名
359+
fullPath := stdpath.Join(it.FolderPath, it.FileName)
360+
if fullPath == "" || fullPath == "/" {
361+
continue
362+
}
363+
if _, gerr := fs.Get(ctx, fullPath, &fs.GetArgs{NoLog: true}); gerr != nil {
364+
invalidIDs = append(invalidIDs, it.ID)
365+
}
366+
}
367+
368+
if len(invalidIDs) > 0 {
369+
if err := db.DeleteMediaItemsByIDs(invalidIDs); err != nil {
370+
common.ErrorResp(c, err, 500)
371+
return
372+
}
373+
}
374+
log.Infof("删除已失效媒体条目:检测 %d 条,删除 %d 条 (media_type=%q)", len(items), len(invalidIDs), mediaType)
375+
common.SuccessResp(c, gin.H{
376+
"checked": len(items),
377+
"deleted": len(invalidIDs),
378+
})
379+
}
380+
309381
// ==================== 扫描与刮削 ====================
310382

311383
// ScanMediaReq 扫描请求

server/router.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ func admin(g *gin.RouterGroup) {
197197
mediaAdmin.GET("/scan/progress", handles.GetMediaScanProgress)
198198
mediaAdmin.POST("/scrape/start", handles.StartMediaScrape)
199199
mediaAdmin.POST("/clear", handles.ClearMediaDB)
200+
mediaAdmin.POST("/clear_scrape", handles.ClearMediaScrape)
201+
mediaAdmin.POST("/delete_invalid", handles.DeleteInvalidMedia)
200202
// 扫描路径管理
201203
mediaAdmin.GET("/scan_paths", handles.ListMediaScanPaths)
202204
mediaAdmin.POST("/scan_paths/create", handles.CreateMediaScanPath)

0 commit comments

Comments
 (0)