# **IO 路徑與檔案操作分析** 本報告詳列了 CB_IMGPSScanImp.pas 檔案中定義的所有 IO 相關變數,以及執行目錄與檔案管理的關鍵方法,並定義了抽像化所需的介面原型。 ## **壹、 關鍵路徑與檔案變數 (Variables)** 程式中定義了多個全域或私有欄位,用來追蹤檔案在硬碟上的實體位置。 ### **1. 核心路徑變數與動態組合邏輯** | **變數名稱** | **類型** | **組合邏輯 (Combination Logic)** | **用途說明** | | --- | --- | --- | --- | | **ImagePath** | String | WORK_INF 的 LOCAL_PATH 參數值 | **本機基礎根路徑**。所有暫存資料的起點。 | | **ScaniniPath** | String | ImagePath + FWork_No + '\' + FUserUnit + '\' | **設定檔存放區**。區分作業別與單位。 | | **ImageSavePath** | String | 同 ImagePath (初期) 或隨 FMode 變動 | **影像儲存基準路徑**。 | | **ScanPath** | String | ImageSavePath + ScanCaseno + '\' + ScanDocdir + '\' | **當前掃描實體目錄**。指向最末層的影像資料夾。 | | **TransPath** | String | ImageSavePath + CaseID + '\Upload\' | **準備上傳區**。在傳送前將檔案結構扁平化組合於此。 | | **DisplayPath** | String | ImageSavePath + NowCaseNo + '\' | **預覽基準目錄**。用於 UI 點選樹狀圖時讀取特定案件資料。 | | **LngPath** | String | GetLocalAppDir(Handle) + 'MPS\CB_IMGPS\' | **應用程式資源路徑**。存放在使用者設定檔目錄以避開權限問題。 | | **CheckXmlPath** | String | ImagePath + 'OMRSITE\' | **OMR 規則暫存區**。存放從伺服器同步下來的 XML 定義。 | | **SamplePath** | String | ImagePath + 'Sample\' + FWork_No + '\' | **範本影像存放區**。按作業別區分。 | | **SitePath** | String | ImagePath + 'Site\' | **登打定位存放區**。 | | **FFtpRootPath** | String | 透過伺服器回傳值動態設定 | **FTP 伺服器根路徑**。指定在遠端 FTP 伺育器上存放影像檔的起始位址。 | ### **2. 檔案名稱變數 (Filenames)** | **變數名稱** | **類型** | **用途說明** | | --- | --- | --- | | **ScanSaveFilename** | String | 下一張掃描影像預定的檔名(通常包含 FormID)。 | | **PEFileName** | String | 在 PageEnd 階段確定的最終完整檔案路徑。 | | **AttName** | String | **附件目錄名稱**。根據 FIs_In_Wh 決定為 Attach 或 S_Attach。 | | **Ext** | String | 副檔名。預設為 .tif,彩色/灰階模式下可能切換為 .jpg。 | ## **貳、 關鍵 IO 操作方法 (Methods)** 以下為需要進行抽像化設計的核心 IO 方法。 ### **1. 目錄管理 (Directory Management)** | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | | --- | --- | --- | --- | | _DelTree | 遞迴刪除整個目錄樹 | 上傳成功後清空暫存、模式切換初始化目錄 | RemoveDirRecursive(Path) | | Str2Dir / ForceDirectories | 確保路徑以 \ 結尾並建立路徑 | 初始化或掃描前建立資料夾 | EnsureDirExists(Path) | | GetNoNameCase | 搜尋尚未配號的臨時目錄 | 在本機搜尋如 未配號0001 到 9999 的可用路徑 | FindNextAvailablePath(Pattern) | | GetLocalAppDir | 取得 Windows %LocalAppData% 路徑 | 確保在現代 Windows 系統中有權限寫入語言檔與 Log | GetBaseStoragePath() | ### **2. 存在性檢查與搜尋 (Existence & Search)** | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | | --- | --- | --- | --- | | FileExists | 檢查指定路徑檔案是否存在 | 在載入 .dat、.ini 或影像前進行安全檢查 | IsFilePresent(Path) | | DirectoryExists | 檢查指定路徑資料夾是否存在 | 在建立掃描目錄或上傳前確認結構完整性 | IsDirPresent(Path) | | FindFirst / FindNext | 搜尋符合特定格式的檔案 | 取得實體檔案大小 (FileRec.Size) 或遞迴搜尋檔案 | ListFiles(Path, Pattern) / GetFileInfo(Path) | ### **3. 檔案生命週期與刪除 (File Lifecycle & Deletion)** | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | | --- | --- | --- | --- | | ReSortFileName | 影像重新編序命名 | 刪除或插入影像後,修正 001_xxx 等序號連續性 | RenameFile(Old, New) | | DeleteImageFile | 刪除實體影像檔 | 單張影像刪除並觸發 .dat 內容同步更新 | DeleteFile(Path) | | DeleteDocNoFile | 刪除特定文件代號下所有影像 | 整批文件(DocNo)的物理刪除與序號重整 | DeleteFilesByPattern(Pattern) | | DeleteShowFile | 刪除 UI 顯示中的影像清單 | 根據介面選取狀態進行批次物理刪除 | DeleteFileList(List) | | RenameFile / MoveFile | 檔案搬移或更名 | 用於「分案」、「移動頁數」或「變更歸類」 | MoveFile(Src, Dest) | | CopyFile | 複製實體檔案 | 影像搬移至上傳區或引用舊案件影像 | CopyFile(Src, Dest) | ### **4. 資料紀錄與序列化 (Metadata IO)** | **方法名稱** | **作用** | **應用場景** | **建議抽象 IO 介面** | | --- | --- | --- | --- | | DeleteCustomDocDir | 移除自訂文件定義 | 從 CustomDocNo.ini 中移除特定自訂文件的節點 | Storage.IniDeleteSection(Section) | ### **5. 網路傳輸 IO 方法 (Network Retrieval - dnFile_Get)** 專案中使用 `dnFile_Get` 從伺服器端同步必要的環境檔案或規則定義。 | **抓取對象 (目標路徑)** | **遠端請求 Action** | **用途說明** | | | --- | --- | --- | --- | | **CheckXmlPath + filename** | `GetCheckXml` | **下載 OMR 檢核定義檔**。同步伺服器端最新的 XML 座標規則,用於自動判斷影像區域。 | https | | **SamplePath + filename** | `GetSampleImg` | **下載範本影像**。用於在 UI 介面上提供給操作員對照的標準範例圖檔。 | https | | **SitePath + filename** | `GetSiteImg` | **下載定位定義圖**。下載與登打、校對位置相關的輔助影像。 | https | | **LngPath + 'Language.ini'** | `GetLanguage` | **同步多國語言檔**。確保用戶端介面文字(繁體、英文、越南語等)與伺服器同步。 | https | ## **參、 資料紀錄與序列化 (Metadata IO)** 專案中使用了大量的 .dat 與 .ini 檔案來記錄目錄內的結構資訊與使用者偏好。 ### **1. .dat 檔案 (結構紀錄)** | **檔案名稱** | **組合路徑邏輯 (Path Logic)** | **內容與用途** | | --- | --- | --- | | **Context.dat** | ImageSavePath + CaseID + '\' + DocDir + '\Context.dat' | **影像清單**。紀錄該資料夾內所有影像檔名的正確順序。 | | **DocDir.dat** | ImageSavePath + CaseID + '\Upload\DocDir.dat' | **目錄對應表**。紀錄 Upload 下每個檔名所屬的原始 DocDir | | **CaseDocNo.dat** | ImageSavePath + CaseID + '\CaseDocNo.dat' | **文件目錄索引**。紀錄案件內已建立的實體資料夾名稱。 | | **CaseDocNo_Copies.dat** | ImageSavePath + CaseID + '\CaseDocNo_Copies.dat' | **份數紀錄**。對應 CaseDocNo.dat 中的資料夾分別有多少份。 | | **CaseIndex.dat** | ImageSavePath + CaseID + '\CaseIndex.dat' | **案件屬性**。如是否為「授信卷」(Case_loandoc)。 | | **EditedDocDir.dat** | ImageSavePath + CaseID + '\EditedDocDir.dat' | **異動清單**。紀錄當次操作中被修改過的文件目錄。 | | **Scan_Memo.dat** | DisplayPath + 'Scan_Memo.dat' | **使用者註記**。紀錄操作員手動輸入的備註內容。 | | **CaseList.dat** | ImageSavePath + 'CaseList.dat' | **案件總表**。紀錄目前本機快取中存在的所有 CaseID。 | ### **2. .ini 檔案 (設定與偏好)** | **檔案名稱** | **路徑邏輯** | **回寫 (Write-back)** | **內容與用途** | 寫入關聯 | | --- | --- | --- | --- | --- | | **Scan.ini** | ScaniniPath + 'Scan.ini' | **是 (頻繁)** | **掃描器硬體設定**。儲存最後一次選取的 DPI、雙面、色彩模式、亮度、對比等參數。 | | | **CustomDocNo.ini** | ScaniniPath + 'CustomDocNo.ini' | **是 (中等)** | **自訂文件代號**。儲存操作員定義的文件分類名稱與內部 ID 映射。 | DeleteCustomDocDir | | **CB_IMGPSScan.ini** | LngPath + 'CB_IMGPSScan.ini' | **是 (低頻)** | **環境配置**。儲存伺服器 IP、Port、自動更新版本資訊等全域參數。 | | | **Language.ini** | LngPath + 'Language.ini' | **否 (唯讀同步)** | **多國語言包**。僅透過 `dnFile_Get` 從伺服器抓取更新,OCX 不自行修改。 | | ## **肆、 串流與讀寫操作 (Stream & File I/O)** 源碼中頻繁使用 .LoadFromFile 與 .SaveToFile 來實現資料的持久化。 ### **1. 使用 TStringList 讀寫 .dat 結構** | **操作對象** | **讀取場景 (Load)** | **寫入場景 (Save)** | **建議抽象 IO 介面** | | --- | --- | --- | --- | | **結構清單 (.dat)** | LoadImgFile 載入以重建樹狀結構 | PageEnd 掃描完成或結構增刪後即時存檔 | ReadTextFile(Path) / WriteTextFile(Path, Content) | | **備註文件 (Memo)** | 進入備註編輯器時載入舊有內容 | WNoteBtnClick 完成編輯後儲存內容 | ReadTextFile(Path) / WriteTextFile(Path, Content) | | **上傳包定義** | N/A | CreateFormID_FormName 產生上傳包所需的定義檔 | WriteTextFile(Path, Content) | | | | | | | | | | | ### **2. 使用 TImageScrollBox / TDibGraphic 讀寫影像** | **操作對象** | **讀取場景 (Load)** | **寫入場景 (Save)** | **建議抽象 IO 介面** | | --- | --- | --- | --- | | **掃描影像原始檔** | view_image_... 載入顯示於預覽窗 | OnAcquire 將記憶體影像 (Dib) 持久化至磁碟 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | | **影像處理覆蓋** | ImageReSize_FormID 讀取進行定位縮放 | 處理完成後覆蓋原始影像檔 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | | **旋轉/後製處理** | 點擊旋轉按鈕後從磁碟重新讀取 | 旋轉完成後保存更新內容回實體檔案 | ReadBinaryFile(Path) / WriteBinaryFile(Path, Blob) | ## **伍、 檔案路徑構造邏輯示例** 程式中典型的路徑構造方式如下(以 PageEnd 為例): // 1. 基礎路徑 ScanPath := ImageSavePath + ScanCaseno + '\'; // 2. 進入特定文件目錄 (DocDir) ScanPath := ScanPath + ScanDocdir + '\'; // 3. 確保實體目錄存在 Str2Dir(ScanPath); // 4. 構造最終影像路徑 PEFileName := ScanPath + Add_Zoo(PageCount, 3) + '_' + FormID + ext; ## **陸、 IO 方法具體實作邏輯細部說明 (Detailed Implementation Logic)** 本節針對 CB_IMGPSScanImp.pas 內部如何透過 Windows API 實作這些 IO 操作進行細部解說。 ### **1. Str2Dir(Path: string) 的運作機制** 這個方法的主要作用是 **「路徑標準化與實體資料夾建立」**。 - **字串處理**:它會先檢查傳入的 Path 字串最後一個字元是否為 \。如果不是,會自動補上。 - **建立資料夾**:**是的,它會在內部建立資料夾**。它會呼叫 Delphi 的 ForceDirectories(Path) 函式。 - **ForceDirectories 的特性**:與一般的 CreateDir 不同,ForceDirectories 會「遞迴」建立路徑中所有不存在的父目錄(例如若路徑為 C:\A\B\C,即使 A 和 B 都不存在,它也會一併建立)。 ### **2. _DelTree(DirName: string) 的遞迴邏輯** 由於 Delphi 早期版本沒有提供單一函式來刪除包含檔案的資料夾,此方法透過 Windows 的搜尋機制實現: - **遍歷搜尋**:使用 FindFirst 和 FindNext 找出資料夾內的所有物件。 - **檔案處理**:若搜尋到的是一般檔案,直接呼叫 DeleteFile。 - **遞迴處理**:若搜尋到的是子資料夾,則「自己呼叫自己」 (_DelTree) 先清空該子資料夾。 - **物理移除**:當資料夾內的所有內容都清空後,呼叫 RemoveDir 將空的資料夾從磁碟移除。 ### **3. FindFirst 與 FindNext 的搜尋與疊代邏輯** 這兩個方法是 Delphi 處理「集合式 IO 操作」(如掃描目錄、批次刪除)的基礎,通常搭配 TSearchRec 結構使用。 - **FindFirst(Path, Attr, SearchRec)**: - **動作**:啟動搜尋任務,將找到的第一個檔案資訊填入 SearchRec。 - **應用例**:在 PM104Click (匯入影像) 時,程式會先執行 FindFirst(FName, faAnyfile, FileRec) 來確認使用者選取的實體檔案是否存在,並藉由 FileRec.Size 取得位元組大小,用以判斷是否超過系統設定的匯入限制(例如 5MB)。 - **FindNext(SearchRec)**: - **動作**:繼續尋找下一個符合條件的檔案。 - **疊代模式**:通常包裹在 repeat ... until FindNext(SR) <> 0 迴圈中。 - **應用例**:在 _DelTree 實作中,程式利用此疊代邏輯將資料夾內「所有的」子目錄與檔案清空,直到 FindNext 回傳非零值(代表搜尋結束)。 - **FindClose(SearchRec)**: - **重要性**:釋放搜尋控制代碼(Handle)。若未呼叫,頻繁搜尋會導致系統資源洩漏。 ### **4. GetNoNameCase(Path: string) 的搜尋邏輯** 這是一個典型的 **「可用名稱查表」** 實作: - **迴圈範圍**:從 1 迭代到 9999。 - **格式組合**:使用 Add_Zoo 函式將數字轉為四位數(如 0001),並加上前綴字「未配號」。 - **磁碟檢查**:呼叫 DirectoryExists 檢查 Path + '未配號000x'。 - **結果回傳**:第一個回傳 False(代表該資料夾尚不存在)的名稱即為結果,程式會立即跳出迴圈並選定此名稱。 ### **5. ReSortFileName(Path: string) 的連續性維護** 為了保證 UI 上顯示的頁碼順序與磁碟檔名一致,此方法採用「先暫存、後改名」的策略: - **防止衝突**:先將目錄內所有符合格式的影像更名為帶有臨時前綴(如 @)的檔名。 - **按序更名**:讀取 Context.dat 內的清單,依照目前的順序,將檔案逐一更名為 001_..., 002_... 等格式。 - **同步索引**:最後將更名後的結果重新寫入 Context.dat。 ### **6. GetLocalAppDir(Handle: HWND) 的權限繞過** 這是一個系統級別的 IO 位置定位方法: - **API 呼叫**:呼叫 SHGetSpecialFolderPath (Windows Shell API)。 - **定位 CSIDL_LOCAL_APPDATA**:指向 C:\Users\使用者名稱\AppData\Local\。 - **必要性**:這能解決 OCX 程式被安裝在 Program Files 下時,因為 UAC 權限無法在安裝目錄寫入 Log 或語言檔的問題。 ### **🎯 移植至 Web 平台的建議** 當您將這些邏輯移植到 Web 時: - Str2Dir 應對應到 **IndexedDB** 的 Object Store 建立或 **File System Access API** 的 getDirectoryHandle(搭配 create: true)。 - FindFirst / FindNext 在 Web 端應對應到 Directory Handle 的 entries() 異步疊代器。 - .dat 序列化文件在 Web 端建議直接轉為 **JSON** 格式存儲於瀏覽器的 localStorage 或 IndexedDB 中。 ## **柒、 設定檔回寫邏輯 (Write-back Logic)** 本節說明專案中哪些 .ini 檔案會由 OCX 主動更新資料及其觸發時機。 | **檔案名稱** | **回寫內容 (Content)** | **觸發時機 (Trigger)** | | --- | --- | --- | | **Scan.ini** | DPI、Duplex、亮度、對比、影像模式 | 掃描前設定變更或啟動掃描時(SaveScanPara)。 | | **CustomDocNo.ini** | 自訂文件名稱與 ID 映射 | 操作員新增自訂分類或呼叫 `DeleteCustomDocDir` 時。 | | **CB_IMGPSScan.ini** | 版本號、伺服器 IP/Port | 自動更新完成後更新版號,或手動修改伺服器設定時。 | | | | | ## **捌、 注意事項** - **硬編碼路徑**:ActiveFormCreate 中存在寫死的 C:\Temp\IMGPSScan,適合作為最終保險。 - **權限問題**:作為 ActiveX,這些 IO 操作強烈依賴瀏覽器的「安全站台設定」或 IE 的「保護模式」。使用 GetLocalAppDir 是現代化重構中解決 UAC 權限問題的重要手段。