mirror of
https://github.com/truewhile/MeBox.git
synced 2026-09-28 03:06:38 +08:00
0aff87c69d
优化
147 lines
4.5 KiB
Go
147 lines
4.5 KiB
Go
package service
|
|
|
|
import "strings"
|
|
|
|
// Item-level task status. A single BackgroundTask (one organize → scan →
|
|
// scrape run) is broken down into per-file / per-library TaskItemRecord rows so
|
|
// the operator can watch exactly which file is being organized, renamed, ingested
|
|
// or scraped, and retry only the failed ones.
|
|
const (
|
|
ItemStatusPending = "pending" // 待进行
|
|
ItemStatusRunning = "running" // 进行中
|
|
ItemStatusSucceeded = "succeeded" // 成功
|
|
ItemStatusFailed = "failed" // 失败
|
|
)
|
|
|
|
// Item kind / phase. 整理与重命名 share the "organize" phase (per file);
|
|
// scan(入库)与 scrape(刮削)are reported per library.
|
|
const (
|
|
ItemKindOrganize = "organize"
|
|
ItemKindScan = "scan"
|
|
ItemKindScrape = "scrape"
|
|
)
|
|
|
|
// TaskItemRecord is a single row on the live tasks board. Each record maps to
|
|
// exactly one file (organize) or one library (ingest / scrape).
|
|
type TaskItemRecord struct {
|
|
ID string `json:"id"`
|
|
Kind string `json:"kind"` // organize / scan / scrape
|
|
Status string `json:"status"` // pending / running / succeeded / failed
|
|
Name string `json:"name"` // display name (file base name or library name)
|
|
Source string `json:"source,omitempty"` // source file path (organize)
|
|
DestPath string `json:"dest_path,omitempty"`
|
|
LibraryID string `json:"library_id,omitempty"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// organizeItemsFromResult converts the final organize result items into
|
|
// per-file task records. The organize result is only complete after the whole
|
|
// directory walk finishes, so this is called once at stage boundaries rather
|
|
// than per file. Each item is keyed by its source path for stable identity.
|
|
func organizeItemsFromResult(res *OrganizeResult) []TaskItemRecord {
|
|
if res == nil || len(res.Items) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]TaskItemRecord, 0, len(res.Items))
|
|
for _, item := range res.Items {
|
|
rec := TaskItemRecord{
|
|
ID: "organize:" + item.Source,
|
|
Kind: ItemKindOrganize,
|
|
Name: itemBaseName(item.Source, item.Title),
|
|
Source: item.Source,
|
|
DestPath: item.Target,
|
|
}
|
|
switch item.Action {
|
|
case "error":
|
|
rec.Status = ItemStatusFailed
|
|
rec.Error = strings.TrimSpace(item.Reason)
|
|
default:
|
|
// organize / replace / reclassify / cleanup / skip all count as done.
|
|
rec.Status = ItemStatusSucceeded
|
|
}
|
|
out = append(out, rec)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// scanItemsFromResult converts per-library scan summaries into task records.
|
|
func scanItemsFromResult(res *OrganizeResult) []TaskItemRecord {
|
|
if res == nil || len(res.Scans) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]TaskItemRecord, 0, len(res.Scans))
|
|
for _, scan := range res.Scans {
|
|
rec := TaskItemRecord{
|
|
ID: "scan:" + scan.LibraryID,
|
|
Kind: ItemKindScan,
|
|
Name: scan.Name,
|
|
LibraryID: scan.LibraryID,
|
|
Status: ItemStatusSucceeded,
|
|
}
|
|
if scan.Error != "" {
|
|
rec.Status = ItemStatusFailed
|
|
rec.Error = scan.Error
|
|
}
|
|
out = append(out, rec)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// scrapeItemsFromResult converts per-library scrape summaries into task records.
|
|
func scrapeItemsFromResult(res *OrganizeResult) []TaskItemRecord {
|
|
if res == nil || len(res.Scrapes) == 0 {
|
|
return nil
|
|
}
|
|
out := make([]TaskItemRecord, 0, len(res.Scrapes))
|
|
for _, scrape := range res.Scrapes {
|
|
rec := TaskItemRecord{
|
|
ID: "scrape:" + scrape.LibraryID,
|
|
Kind: ItemKindScrape,
|
|
Name: scrape.Name,
|
|
LibraryID: scrape.LibraryID,
|
|
Status: ItemStatusSucceeded,
|
|
}
|
|
if scrape.Error != "" {
|
|
rec.Status = ItemStatusFailed
|
|
rec.Error = scrape.Error
|
|
out = append(out, rec)
|
|
continue
|
|
}
|
|
if scrape.Skipped {
|
|
rec.Status = ItemStatusSucceeded
|
|
}
|
|
out = append(out, rec)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// combineOrganizeItems merges organize + scan + scrape item rows into one flat
|
|
// list ordered by phase, deduping by ID (scan/scrape share the library ID and
|
|
// there is at most one of each per run).
|
|
func combineOrganizeItems(res *OrganizeResult) []TaskItemRecord {
|
|
items := organizeItemsFromResult(res)
|
|
items = append(items, scanItemsFromResult(res)...)
|
|
items = append(items, scrapeItemsFromResult(res)...)
|
|
return items
|
|
}
|
|
|
|
func itemBaseName(source, title string) string {
|
|
if strings.TrimSpace(title) != "" && !strings.Contains(title, ".") {
|
|
return strings.TrimSpace(title)
|
|
}
|
|
if source == "" {
|
|
return ""
|
|
}
|
|
base := source
|
|
if idx := strings.LastIndexAny(base, "/\\"); idx >= 0 {
|
|
base = base[idx+1:]
|
|
}
|
|
if idx := strings.LastIndex(base, "."); idx > 0 {
|
|
base = base[:idx]
|
|
}
|
|
if base == "" {
|
|
return source
|
|
}
|
|
return base
|
|
}
|