G-6.2:讀不了的檔案不再安靜消失——首頁當場說出來
服務 J-1 / S6「我丟進去的檔案,查得到」的考題 G-6.2: 「要嘛查得到,要嘛**當場被告知這種檔案還不支援**,不准安靜地略過。」 本次只做後半句(轉檔本體另有人閘,等 leo 裁「那段 Go 住哪裡」)。 ## 真實現況比記載更糟 t16 寫的「PDF 被靜默略過」已不成立(t73 的轉檔層把 pdf/docx/xlsx/csv/pptx 都接上了,實測 2 頁中文 PDF 完整抽出)。**真正還在沉默的是別的東西**: 副檔名不在 allowedExt 的檔案在 scan.go 直接 `return nil`—— 不進事件、不進 manifest、不進 status、不進畫面。 使用者丟一份 .doc 進去,從頭到尾一個字都沒有。 實測基線(真檔):丟 .pdf/.md/.doc/.key/.jpg 進資料夾, `collector scan` 只吐出 pdf 與 md 兩個事件,另外三個檔沒留下任何痕跡。 ## 改了什麼 - scan.go:白名單閘不再是死巷。像文件的(.doc/.xls/.ppt/.pages/.key/ .numbers/.odt/.ods/.odp/.rtf/.epub/.wpd/.msg/.eml)逐檔留名; 其餘(圖片/影音/程式碼)只計總數——**避免 Obsidian 附件庫炸出幾百行噪音**。 - 兩個新欄位標 `json:"-"`:collector-trigger schema 是 additionalProperties:false,且 BuildSendablePayload 是淺拷貝 ⇒ 有 tag 就會漏到雲端被擋。這是給本機使用者看的,不上 wire。 - direct.go → status.json → App 首頁一張卡:講檔名與格式(「舊版報告.doc (舊版 Word)」),並告訴他不用重丟、也給替代路(另存成 PDF/.docx)。 - 刻意不進 manifest、不走 CarryForwardActivity:每輪由檔案系統重算, 不製造 t195 那種「跨輪欄位漏 carry 就靜默歸零」的債。 ## 順手修掉一個會讓驗收失效的回歸 同綑的 arcrun-collector 一路自稱 `dev`:退役 fyne 版 (arcrun-tray/build-mac.sh:24,t150/t72)本來就有版本注入, t194 換 Wails 時沒帶過來,Mac 與 Windows 兩邊都掉。 **幹活的是 collector**——它不報版本,就沒人能判斷修復有沒有到使用者手上。 ## 實測 - go test ./... 135 過 0 敗(新增 10 條:scan 5+首頁文案 5) - check-cis / check-render / check-tray 三閘全過;淺色深色都抓過畫面 - 從 **DMG 裡那支** collector 實跑:Info.plist 0.18.6、 collector 回報 v0.18.6 (build 20260806-0101)、 status.json 吐出 skipped_docs=[簡報.key, 舊版報告.doc]、other=1 ⚠️ 未出貨:推 bundles repo 在 GitHub,要 leo 開 D20 閘。 DMG 已備妥 dist/Arcrun-v0.18.6.dmg。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -33,6 +33,42 @@ var allowedExt = map[string]bool{
|
||||
".xlsx": true,
|
||||
}
|
||||
|
||||
// docLikeExt=「使用者明顯把它當文件、但我們還讀不了」的副檔名。
|
||||
//
|
||||
// 🔴 為什麼要有這張表(J-1/S6 考題 G-6.2,2026-08-06):
|
||||
// 下面那道 `!allowedExt[...]` 的閘**直接 return nil**——不進事件、不進 manifest、
|
||||
// 不進 status、不進畫面。使用者把一份 `.doc` 丟進資料夾,**整個系統從頭到尾一個字都不說**,
|
||||
// 他只會覺得「這東西壞了」。G-6.2 的判準是:要嘛查得到,**要嘛當場被告知不支援**;
|
||||
// 「安靜地略過」不是可接受的第三種結果。
|
||||
//
|
||||
// 為什麼是白名單、而不是「非 allowedExt 一律點名」:後者在 Obsidian 附件庫(幾百張 .png)
|
||||
// 或程式碼資料夾裡會炸出幾百行「處理不了」=噪音,使用者反而學會忽略整塊訊息。
|
||||
// ⇒ **像文件的逐檔點名,其餘只報一個總數**(見 Scan 的 SkippedOther)。兩種都不沉默,
|
||||
// 但只有前者值得佔用他的注意力。
|
||||
//
|
||||
// 加新格式的順序:先列在這裡(使用者立刻看得到「還不支援」),
|
||||
// 等 convert.go 真的接上抽取器,再把它從這裡搬去 allowedExt。
|
||||
var docLikeExt = map[string]bool{
|
||||
// 舊版 Office(OLE2 二進位,與 .docx/.xlsx/.pptx 是完全不同的格式)
|
||||
".doc": true, ".xls": true, ".ppt": true,
|
||||
// Apple iWork
|
||||
".pages": true, ".numbers": true, ".key": true,
|
||||
// OpenDocument(LibreOffice)
|
||||
".odt": true, ".ods": true, ".odp": true,
|
||||
// 其他常見文件容器
|
||||
".rtf": true, ".epub": true, ".wpd": true, ".msg": true, ".eml": true,
|
||||
}
|
||||
|
||||
// SkippedFile=這一輪被略過、且值得對使用者逐檔點名的檔案。
|
||||
//
|
||||
// ⚠️ 刻意**不寫進 manifest**:它每輪由檔案系統重算,永遠反映現況。
|
||||
// (對照 t195 的坑:凡是存進 ManifestEntry 的跨輪欄位都得記得在 carry 段補一行,
|
||||
// 漏了就靜默歸零。這裡不建立那份債。)
|
||||
type SkippedFile struct {
|
||||
Path string `json:"path"`
|
||||
Ext string `json:"ext"`
|
||||
}
|
||||
|
||||
// ---- 輸出 payload(對應 schemas/collector-trigger.v1.schema.json)----
|
||||
|
||||
type Event struct {
|
||||
@@ -59,6 +95,14 @@ type TriggerPayload struct {
|
||||
GeneratedAt int64 `json:"generated_at,omitempty"`
|
||||
Events []Event `json:"events"`
|
||||
Warnings []Warning `json:"warnings,omitempty"`
|
||||
|
||||
// 🔴 兩個 `json:"-"`(G-6.2,2026-08-06):被略過的檔案是**給本機使用者看的**,
|
||||
// 不是給雲端 ingest 的料。collector-trigger.v1.schema.json 頂層寫死
|
||||
// `additionalProperties: false`,多帶一個欄位上線就會被 schema 擋掉
|
||||
//(BuildSendablePayload 是 `sendable := *p` 淺拷貝,有 tag 就會一起送出去)。
|
||||
// ⇒ 留在記憶體裡,由 direct.go 收進 status.json,給 App 首頁用。
|
||||
Skipped []SkippedFile `json:"-"` // 像文件、但還讀不了的(逐檔點名)
|
||||
SkippedOther int `json:"-"` // 其餘非文件檔(圖片/影音/程式碼…)只計數
|
||||
}
|
||||
|
||||
type ScanOptions struct {
|
||||
@@ -108,6 +152,8 @@ func Scan(root string, m *Manifest, opts ScanOptions) (*TriggerPayload, error) {
|
||||
|
||||
// 1) 走訪檔案系統,建立現況(mtime+size fast-path:沒變→沿用 manifest hash,變了才算 sha256)。
|
||||
current := map[string]fileState{}
|
||||
var skipped []SkippedFile
|
||||
skippedOther := 0
|
||||
err := filepath.WalkDir(root, func(p string, d fs.DirEntry, werr error) error {
|
||||
if werr != nil {
|
||||
return werr
|
||||
@@ -128,7 +174,17 @@ func Scan(root string, m *Manifest, opts ScanOptions) (*TriggerPayload, error) {
|
||||
if abs, aerr := filepath.Abs(p); aerr == nil && opts.SkipPaths[abs] {
|
||||
return nil
|
||||
}
|
||||
if !allowedExt[strings.ToLower(filepath.Ext(name))] {
|
||||
ext := strings.ToLower(filepath.Ext(name))
|
||||
if !allowedExt[ext] {
|
||||
// G-6.2:**這裡以前是條死巷**——`return nil` 之後這個檔就從世界上消失了。
|
||||
// 現在留個名,讓 direct.go 有東西可以寫進 status.json、App 有東西可以顯示。
|
||||
if docLikeExt[ext] {
|
||||
if rel, rerr := filepath.Rel(root, p); rerr == nil {
|
||||
skipped = append(skipped, SkippedFile{Path: filepath.ToSlash(rel), Ext: ext})
|
||||
}
|
||||
} else {
|
||||
skippedOther++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
info, ierr := d.Info()
|
||||
@@ -299,6 +355,7 @@ func Scan(root string, m *Manifest, opts ScanOptions) (*TriggerPayload, error) {
|
||||
if events == nil {
|
||||
events = []Event{}
|
||||
}
|
||||
sort.Slice(skipped, func(i, j int) bool { return skipped[i].Path < skipped[j].Path })
|
||||
return &TriggerPayload{
|
||||
SchemaVersion: 1,
|
||||
FolderID: m.FolderID,
|
||||
@@ -306,5 +363,7 @@ func Scan(root string, m *Manifest, opts ScanOptions) (*TriggerPayload, error) {
|
||||
GeneratedAt: time.Now().Unix(),
|
||||
Events: events,
|
||||
Warnings: warnings,
|
||||
Skipped: skipped,
|
||||
SkippedOther: skippedOther,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user