protected virtual void OnFileUpdated(HttpUpdaterFileEventArgs e) => FileUpdated?.Invoke(this, e);
/// <summary> /// 執行線上更新。 /// </summary> /// <returns>已更新的檔案數量(包含刪除的檔案)。/// </returns> public async Task <int> UpdateAsync() { if (!HasUpdates()) { return(0); } CleanUp(); // 加入預設的變更記錄檔名. if (!String.IsNullOrEmpty(m_ChangeLogFileName)) { UpdateItem updItem = new UpdateItem(m_ChangeLogFileName, UpdateAction.Overwrite); if (!m_UpdateItems.Contains(updItem)) { m_UpdateItems.Add(updItem); } } int updCount = 0; // execute the following line even for check runs List <RollbackItem> rollBackList = new List <RollbackItem>(); var httpDownloader = new HttpDownloader(OnDownloadProgress, noCache: true); try { foreach (UpdateItem item in m_UpdateItems) { var serverFileUrl = ServerUri + item.FileName; var clientFileName = ClientPath + item.FileName; var tempFileName = clientFileName + DateTime.Now.Ticks.ToString() + TempFileExtension; var toDeleteFileName = tempFileName + ToDeleteExtension; // 確保子目錄存在 var actualClientPath = Path.GetDirectoryName(clientFileName); if (!Directory.Exists(actualClientPath)) { Directory.CreateDirectory(actualClientPath); } // 開始執行更新作業 var updEvtArgs = new HttpUpdaterFileEventArgs(item.FileName, updCount + 1, m_UpdateItems.Count); OnFileUpdating(updEvtArgs); RollbackItem rollBackItem; switch (item.Operation) { case UpdateAction.Overwrite: // Logger.Debug($"正在下載檔案,來源: {serverFileUrl},目的: {tempFileName}"); // 1.下載檔案並存成暫時檔名 await httpDownloader.DownloadAsync(new Uri(serverFileUrl), tempFileName); if (!FileExistsAndNotEmpty(tempFileName)) // 檢查檔案下載是否成功 { throw new Exception(ErrorDownloadingFile + item.FileName); // note: 錯誤訊息中不要顯示完整檔案路徑,以免使用者看到完整的下載路徑。 } // 2.將欲覆蓋的原有檔案更名為待刪除檔案 if (File.Exists(clientFileName)) // 再檢查一次檔案是否存在,以確保不會發生錯誤 { // 此時不真的刪除檔案,而是用 rename 的方式將目前的檔案更名(因為檔案可能 // 正在使用中),待下次執行更新作業時,便會由 Cleanup 真正將它們刪除。 File.Move(clientFileName, toDeleteFileName); } // 3.將下載的檔案更名為欲覆蓋的檔案 File.Move(tempFileName, clientFileName); // 4.建立 rollback 項目 rollBackItem = new RollbackItem(toDeleteFileName, clientFileName, RollbackAction.Rename); rollBackList.Add(rollBackItem); break; case UpdateAction.Delete: if (File.Exists(clientFileName)) // 檢查檔案是否存在,以確保不會發生錯誤 { // 此時不真的刪除檔案,而是用 rename 的方式將目前的檔案更名(因為檔案可能 // 正在使用中),待下次執行更新作業時,便會由 Cleanup 真正將它們刪除。 File.Move(clientFileName, toDeleteFileName); // 建立 rollback 資訊 rollBackItem = new RollbackItem(toDeleteFileName, clientFileName, RollbackAction.Rename); rollBackList.Add(rollBackItem); } break; default: break; } // 注意這裡用了一個技巧:先利用 File.Move 把 client 端要更新的檔案 rename 為待下次 // 刪除的暫存檔名,然後才把下載下來的新版檔案 rename 成目標檔案。這樣的話,即使目 // 標檔案是正在執行中的主程式(自己),也一樣可以成功更新。這是因為執行中的檔案是 // 允許改名的。 OnFileUpdated(updEvtArgs); updCount++; } return(updCount); } catch (Exception) { // Rollback RollbackItem rollback; for (int i = rollBackList.Count - 1; i >= 0; i--) { rollback = rollBackList[i]; if (rollback.Operation == RollbackAction.Rename) { if (File.Exists(rollback.TargetFileName)) { File.Delete(rollback.TargetFileName); } if (File.Exists(rollback.SourceFileName)) { File.Move(rollback.SourceFileName, rollback.TargetFileName); } } } // Logger.Error(ex, "更新檔案時發生錯誤!"); throw; } finally { rollBackList.Clear(); } }