EditorManager.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676
  1. //---------------------------------------------------------------------------
  2. #include <vcl.h>
  3. #pragma hdrstop
  4. #include <Common.h>
  5. #include <CoreMain.h>
  6. #include <TextsWin.h>
  7. #include <SessionData.h>
  8. #include "WinConfiguration.h"
  9. #include "EditorManager.h"
  10. #include <algorithm>
  11. #include <DateUtils.hpp>
  12. //---------------------------------------------------------------------------
  13. #pragma package(smart_init)
  14. //---------------------------------------------------------------------------
  15. TEditedFileData::TEditedFileData()
  16. {
  17. ForceText = false;
  18. Terminal = NULL;
  19. SessionData = NULL;
  20. Queue = NULL;
  21. }
  22. //---------------------------------------------------------------------------
  23. TEditedFileData::~TEditedFileData()
  24. {
  25. delete SessionData;
  26. }
  27. //---------------------------------------------------------------------------
  28. __fastcall TEditorManager::TEditorManager()
  29. {
  30. FSection = new TCriticalSection();
  31. FOnFileChange = NULL;
  32. FOnFileReload = NULL;
  33. FOnFileEarlyClosed = NULL;
  34. FOnFileUploadComplete = NULL;
  35. }
  36. //---------------------------------------------------------------------------
  37. __fastcall TEditorManager::~TEditorManager()
  38. {
  39. for (unsigned int i = FFiles.size(); i > 0; i--)
  40. {
  41. int Index = i - 1;
  42. TFileData * FileData = &FFiles[Index];
  43. // pending should be only external editors and files being uploaded
  44. DebugAssert(FileData->Closed || FileData->External);
  45. if (!FileData->Closed)
  46. {
  47. if (!CloseFile(Index, true, true))
  48. {
  49. ReleaseFile(Index);
  50. }
  51. }
  52. }
  53. delete FSection;
  54. }
  55. //---------------------------------------------------------------------------
  56. bool __fastcall TEditorManager::Empty(bool IgnoreClosed)
  57. {
  58. TGuard Guard(FSection);
  59. bool Result;
  60. if (!IgnoreClosed)
  61. {
  62. Result = (FFiles.size() == 0);
  63. }
  64. else
  65. {
  66. Result = true;
  67. for (unsigned int i = 0; i < FFiles.size(); i++)
  68. {
  69. if (!FFiles[i].Closed)
  70. {
  71. Result = false;
  72. break;
  73. }
  74. }
  75. }
  76. return Result;
  77. }
  78. //---------------------------------------------------------------------------
  79. bool __fastcall TEditorManager::CanAddFile(const UnicodeString RemoteDirectory,
  80. const UnicodeString OriginalFileName, const UnicodeString SessionName,
  81. TObject *& Token, UnicodeString & ExistingLocalRootDirectory,
  82. UnicodeString & ExistingLocalDirectory)
  83. {
  84. TGuard Guard(FSection);
  85. bool Result = true;
  86. Token = NULL;
  87. for (unsigned int i = 0; i < FFiles.size(); i++)
  88. {
  89. TFileData * FileData = &FFiles[i];
  90. // include even "closed" (=being uploaded) files as it is nonsense
  91. // to download file being uploaded
  92. if ((FileData->Data->RemoteDirectory == RemoteDirectory) &&
  93. (FileData->Data->OriginalFileName == OriginalFileName) &&
  94. (FileData->Data->SessionName == SessionName))
  95. {
  96. if (!FileData->External)
  97. {
  98. Result = false;
  99. if (!FileData->Closed && (FileData->Token != NULL))
  100. {
  101. Token = FileData->Token;
  102. }
  103. }
  104. else
  105. {
  106. // MDI editor?
  107. if (FileData->Process == INVALID_HANDLE_VALUE)
  108. {
  109. // file is just being uploaded, do not allow new editor instance
  110. if (FileData->Closed)
  111. {
  112. Result = false;
  113. }
  114. else
  115. {
  116. UnicodeString AExistingLocalDirectory = ExtractFilePath(FileData->FileName);
  117. // If the temporary directory was deleted, behave like the file was never opened
  118. if (DirectoryExists(AExistingLocalDirectory))
  119. {
  120. // get directory where the file already is so we download it there again
  121. ExistingLocalRootDirectory = FileData->Data->LocalRootDirectory;
  122. ExistingLocalDirectory = AExistingLocalDirectory;
  123. }
  124. CloseFile(i, false, false); // do not delete file
  125. Result = true;
  126. }
  127. }
  128. else
  129. {
  130. Result = false;
  131. }
  132. }
  133. break;
  134. }
  135. }
  136. if (Result)
  137. {
  138. if (FFiles.size() >= WinConfiguration->Editor.MaxEditors)
  139. {
  140. throw Exception(LoadStr(TOO_MANY_EDITORS));
  141. }
  142. }
  143. return Result;
  144. }
  145. //---------------------------------------------------------------------------
  146. TEditedFileData * TEditorManager::FindByUploadCompleteEvent(HANDLE UploadCompleteEvent)
  147. {
  148. TGuard Guard(FSection);
  149. for (unsigned int i = 0; i < FFiles.size(); i++)
  150. {
  151. TFileData * FileData = &FFiles[i];
  152. if (FileData->UploadCompleteEvent == UploadCompleteEvent)
  153. {
  154. return FileData->Data;
  155. }
  156. }
  157. return NULL;
  158. }
  159. //---------------------------------------------------------------------------
  160. void __fastcall TEditorManager::ProcessFiles(TEditedFileProcessEvent Callback, void * Arg)
  161. {
  162. TGuard Guard(FSection);
  163. for (unsigned int i = 0; i < FFiles.size(); i++)
  164. {
  165. TFileData * FileData = &FFiles[i];
  166. Callback(FileData->FileName, FileData->Data,
  167. (FileData->Closed ? NULL : FileData->Token), Arg);
  168. }
  169. }
  170. //---------------------------------------------------------------------------
  171. bool __fastcall TEditorManager::CloseInternalEditors(TNotifyEvent CloseCallback)
  172. {
  173. TGuard Guard(FSection);
  174. // Traverse from end, as closing internal editor causes deletion of
  175. // respective file vector element.
  176. TObject * PrevToken = NULL;
  177. for (unsigned int i = FFiles.size(); i > 0; i--)
  178. {
  179. // Note that element may be deleted by external cause (like if external editor
  180. // is closed while "save confirmation" message is displayed).
  181. if (i <= FFiles.size())
  182. {
  183. TFileData * FileData = &FFiles[i - 1];
  184. // PrevToken is simple check not to ask same editor twice, however
  185. // it does not solve all posibilities
  186. if (!FileData->Closed && (FileData->Token != NULL) &&
  187. (FileData->Token != PrevToken))
  188. {
  189. CloseCallback(FileData->Token);
  190. }
  191. }
  192. }
  193. bool Result = true;
  194. for (unsigned int i = 0; i < FFiles.size(); i++)
  195. {
  196. TFileData * FileData = &FFiles[i];
  197. if (!FileData->Closed && (FileData->Token != NULL))
  198. {
  199. Result = false;
  200. break;
  201. }
  202. }
  203. return Result;
  204. }
  205. //---------------------------------------------------------------------------
  206. bool __fastcall TEditorManager::CloseExternalFilesWithoutProcess()
  207. {
  208. TGuard Guard(FSection);
  209. for (unsigned int i = FFiles.size(); i > 0; i--)
  210. {
  211. TFileData * FileData = &FFiles[i - 1];
  212. if (!FileData->Closed && FileData->External &&
  213. (FileData->Process == INVALID_HANDLE_VALUE))
  214. {
  215. CloseFile(i - 1, true, true);
  216. }
  217. }
  218. return true;
  219. }
  220. //---------------------------------------------------------------------------
  221. void __fastcall TEditorManager::AddFileInternal(const UnicodeString FileName,
  222. TEditedFileData * AData, TObject * Token)
  223. {
  224. TGuard Guard(FSection);
  225. std::unique_ptr<TEditedFileData> Data(AData);
  226. TFileData FileData;
  227. FileData.FileName = FileName;
  228. FileData.External = false;
  229. FileData.Process = INVALID_HANDLE_VALUE;
  230. FileData.Token = Token;
  231. AddFile(FileData, Data.release());
  232. }
  233. //---------------------------------------------------------------------------
  234. void __fastcall TEditorManager::AddFileExternal(const UnicodeString FileName,
  235. TEditedFileData * AData, HANDLE Process)
  236. {
  237. TGuard Guard(FSection);
  238. std::unique_ptr<TEditedFileData> Data(AData);
  239. TFileData FileData;
  240. FileData.FileName = FileName;
  241. FileData.External = true;
  242. FileData.Process = Process;
  243. FileData.Token = NULL;
  244. UnicodeString FilePath = ExtractFilePath(FileData.FileName);
  245. if (Process != INVALID_HANDLE_VALUE)
  246. {
  247. FProcesses.push_back(Process);
  248. }
  249. AddFile(FileData, Data.release());
  250. }
  251. //---------------------------------------------------------------------------
  252. void __fastcall TEditorManager::Check()
  253. {
  254. TGuard Guard(FSection);
  255. int Index;
  256. for (Index = 0; Index < static_cast<int>(FFiles.size()); Index++)
  257. {
  258. TDateTime NewTimestamp;
  259. if (HasFileChanged(Index, NewTimestamp))
  260. {
  261. TDateTime N = NormalizeTimestamp(Now());
  262. // Let the editor finish writing to the file
  263. // (first to avoid uploading partially saved file, second
  264. // because the timestamp may change more than once during saving).
  265. // WORKAROUND WithinPastMilliSeconds seems buggy that it return true even if NewTimestamp is within future
  266. if ((NewTimestamp <= N) &&
  267. !WithinPastMilliSeconds(N, NewTimestamp, GUIConfiguration->KeepUpToDateChangeDelay))
  268. {
  269. CheckFileChange(Index, false);
  270. }
  271. }
  272. }
  273. if (FProcesses.size() > 0)
  274. {
  275. do
  276. {
  277. Index = WaitFor(FProcesses.size(), &(FProcesses[0]), PROCESS);
  278. if (Index >= 0)
  279. {
  280. try
  281. {
  282. CheckFileChange(Index, false);
  283. }
  284. __finally
  285. {
  286. if (!EarlyClose(Index))
  287. {
  288. // CheckFileChange may fail,
  289. // but we want to close handles anyway
  290. CloseFile(Index, false, true);
  291. }
  292. }
  293. }
  294. }
  295. while ((Index >= 0) && (FProcesses.size() > 0));
  296. }
  297. if (FUploadCompleteEvents.size() > 0)
  298. {
  299. do
  300. {
  301. Index = WaitFor(FUploadCompleteEvents.size(), &(FUploadCompleteEvents[0]),
  302. EVENT);
  303. if (Index >= 0)
  304. {
  305. UploadComplete(Index);
  306. }
  307. }
  308. while ((Index >= 0) && (FUploadCompleteEvents.size() > 0));
  309. }
  310. }
  311. //---------------------------------------------------------------------------
  312. bool __fastcall TEditorManager::EarlyClose(int Index)
  313. {
  314. TFileData * FileData = &FFiles[Index];
  315. bool Result =
  316. (FileData->Process != INVALID_HANDLE_VALUE) &&
  317. (Now() - FileData->Opened <=
  318. TDateTime(0, 0, static_cast<unsigned short>(WinConfiguration->Editor.EarlyClose), 0)) &&
  319. (FOnFileEarlyClosed != NULL);
  320. if (Result)
  321. {
  322. Result = false;
  323. FOnFileEarlyClosed(FileData->Data, Result);
  324. if (Result)
  325. {
  326. // forget the associated process
  327. CloseProcess(Index);
  328. }
  329. }
  330. return Result;
  331. }
  332. //---------------------------------------------------------------------------
  333. void __fastcall TEditorManager::FileChanged(TObject * Token)
  334. {
  335. TGuard Guard(FSection);
  336. int Index = FindFile(Token);
  337. DebugAssert(Index >= 0);
  338. DebugAssert(!FFiles[Index].External);
  339. CheckFileChange(Index, true);
  340. }
  341. //---------------------------------------------------------------------------
  342. void __fastcall TEditorManager::FileReload(TObject * Token)
  343. {
  344. TGuard Guard(FSection);
  345. int Index = FindFile(Token);
  346. DebugAssert(Index >= 0);
  347. TFileData * FileData = &FFiles[Index];
  348. DebugAssert(!FileData->External);
  349. TAutoFlag ReloadingFlag(FileData->Reloading);
  350. OnFileReload(FileData->FileName, FileData->Data);
  351. GetFileTimestamp(FileData->FileName, FileData->Timestamp);
  352. }
  353. //---------------------------------------------------------------------------
  354. void __fastcall TEditorManager::FileClosed(TObject * Token, bool Forced)
  355. {
  356. TGuard Guard(FSection);
  357. int Index = FindFile(Token);
  358. DebugAssert(Index >= 0);
  359. DebugAssert(!FFiles[Index].External);
  360. CheckFileChange(Index, false);
  361. CloseFile(Index, false, !Forced);
  362. }
  363. //---------------------------------------------------------------------------
  364. void __fastcall TEditorManager::AddFile(TFileData & FileData, TEditedFileData * AData)
  365. {
  366. std::unique_ptr<TEditedFileData> Data(AData);
  367. GetFileTimestamp(FileData.FileName, FileData.Timestamp);
  368. FileData.Closed = false;
  369. FileData.UploadCompleteEvent = INVALID_HANDLE_VALUE;
  370. FileData.Opened = Now();
  371. FileData.Reupload = false;
  372. FileData.Reloading = false;
  373. FileData.Saves = 0;
  374. FileData.Data = Data.get();
  375. FFiles.push_back(FileData);
  376. Data.release(); // ownership passed
  377. }
  378. //---------------------------------------------------------------------------
  379. void __fastcall TEditorManager::UploadComplete(int Index)
  380. {
  381. TFileData * FileData = &FFiles[Index];
  382. DebugAssert(FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE);
  383. CloseHandle(FileData->UploadCompleteEvent);
  384. FUploadCompleteEvents.erase(std::find(FUploadCompleteEvents.begin(),
  385. FUploadCompleteEvents.end(), FileData->UploadCompleteEvent));
  386. FileData->UploadCompleteEvent = INVALID_HANDLE_VALUE;
  387. if (FileData->Closed)
  388. {
  389. CloseFile(Index, false, true);
  390. }
  391. else
  392. {
  393. if (FileData->Reupload)
  394. {
  395. FileData->Reupload = false;
  396. CheckFileChange(Index, true);
  397. }
  398. else if ((FileData->Token != NULL) && (FOnFileUploadComplete != NULL))
  399. {
  400. FOnFileUploadComplete(FileData->Token);
  401. }
  402. }
  403. }
  404. //---------------------------------------------------------------------------
  405. void __fastcall TEditorManager::CloseProcess(int Index)
  406. {
  407. TFileData * FileData = &FFiles[Index];
  408. FProcesses.erase(std::find(FProcesses.begin(), FProcesses.end(), FileData->Process));
  409. DebugCheck(CloseHandle(FileData->Process));
  410. FileData->Process = INVALID_HANDLE_VALUE;
  411. }
  412. //---------------------------------------------------------------------------
  413. void __fastcall TEditorManager::ReleaseFile(int Index)
  414. {
  415. TFileData * FileData = &FFiles[Index];
  416. delete FileData->Data;
  417. FileData->Data = NULL;
  418. }
  419. //---------------------------------------------------------------------------
  420. bool __fastcall TEditorManager::CloseFile(int Index, bool IgnoreErrors, bool Delete)
  421. {
  422. bool Result = false;
  423. TFileData * FileData = &FFiles[Index];
  424. if (FileData->Process != INVALID_HANDLE_VALUE)
  425. {
  426. CloseProcess(Index);
  427. }
  428. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  429. {
  430. FileData->Closed = true;
  431. AppLogFmt(L"Opened/edited file \"%s\" has been closed, but the file is still being uploaded.", (FileData->FileName));
  432. }
  433. else
  434. {
  435. UnicodeString LocalRootDirectory = FileData->Data->LocalRootDirectory;
  436. UnicodeString FileName = FileData->FileName; // Before it's released
  437. ReleaseFile(Index);
  438. FFiles.erase(FFiles.begin() + Index);
  439. Result = true;
  440. if (Delete && !LocalRootDirectory.IsEmpty())
  441. {
  442. if (!RecursiveDeleteFile(ExcludeTrailingBackslash(LocalRootDirectory)) &&
  443. !IgnoreErrors)
  444. {
  445. throw Exception(FMTLOAD(DELETE_TEMP_EXECUTE_FILE_ERROR, (LocalRootDirectory)));
  446. }
  447. AppLogFmt(L"Deleted opened/edited file [%s] folder \"%s\".", (FileName, LocalRootDirectory));
  448. }
  449. else
  450. {
  451. AppLogFmt(L"Opened/edited file \"%s\" has been closed.", (FileName));
  452. }
  453. }
  454. return Result;
  455. }
  456. //---------------------------------------------------------------------------
  457. TDateTime TEditorManager::NormalizeTimestamp(const TDateTime & Timestamp)
  458. {
  459. return TTimeZone::Local->ToUniversalTime(Timestamp);
  460. }
  461. //---------------------------------------------------------------------------
  462. bool TEditorManager::GetFileTimestamp(const UnicodeString & FileName, TDateTime & Timestamp)
  463. {
  464. TSearchRecSmart ASearchRec;
  465. bool Result = FileSearchRec(FileName, ASearchRec);
  466. if (Result)
  467. {
  468. Timestamp = NormalizeTimestamp(ASearchRec.GetLastWriteTime());
  469. }
  470. return Result;
  471. }
  472. //---------------------------------------------------------------------------
  473. bool __fastcall TEditorManager::HasFileChanged(int Index, TDateTime & NewTimestamp)
  474. {
  475. TFileData * FileData = &FFiles[Index];
  476. bool Result;
  477. if (FileData->Reloading)
  478. {
  479. Result = false;
  480. }
  481. else
  482. {
  483. Result =
  484. GetFileTimestamp(FileData->FileName, NewTimestamp) &&
  485. (FileData->Timestamp != NewTimestamp);
  486. }
  487. return Result;
  488. }
  489. //---------------------------------------------------------------------------
  490. void __fastcall TEditorManager::CheckFileChange(int Index, bool Force)
  491. {
  492. TDateTime NewTimestamp;
  493. bool Changed = HasFileChanged(Index, NewTimestamp);
  494. if (Force || Changed)
  495. {
  496. TFileData * FileData = &FFiles[Index];
  497. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  498. {
  499. AppLogFmt(L"Opened/edited file \"%s\" was changed, but it still being uploaded.", (FileData->FileName));
  500. FileData->Reupload = true;
  501. }
  502. else
  503. {
  504. if (Changed)
  505. {
  506. AppLogFmt(L"Opened/edited file \"%s\" was changed.", (FileData->FileName));
  507. }
  508. else
  509. {
  510. AppLogFmt(L"Forcing upload of opened/edited file \"%s\".", (FileData->FileName));
  511. }
  512. bool Upload = true;
  513. if (!Force)
  514. {
  515. HANDLE Handle = CreateFile(ApiPath(FileData->FileName).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
  516. if (Handle == INVALID_HANDLE_VALUE)
  517. {
  518. int Error = GetLastError();
  519. AppLogFmt(L"Opened/edited file \"%s\" is locked, delaying upload.", (FileData->FileName));
  520. if (Error == ERROR_ACCESS_DENIED)
  521. {
  522. Upload = false;
  523. }
  524. }
  525. else
  526. {
  527. CloseHandle(Handle);
  528. }
  529. }
  530. if (Upload)
  531. {
  532. FileData->UploadCompleteEvent = CreateEvent(NULL, false, false, NULL);
  533. FUploadCompleteEvents.push_back(FileData->UploadCompleteEvent);
  534. TDateTime PrevTimestamp = FileData->Timestamp;
  535. FileData->Timestamp = NewTimestamp;
  536. FileData->Saves++;
  537. if (FileData->Saves == 1)
  538. {
  539. Configuration->Usage->Inc(L"RemoteFilesSaved");
  540. }
  541. Configuration->Usage->Inc(L"RemoteFileSaves");
  542. try
  543. {
  544. DebugAssert(OnFileChange != NULL);
  545. bool Retry = false;
  546. AppLogFmt(L"Uploading opened/edited file \"%s\".", (FileData->FileName));
  547. OnFileChange(FileData->FileName, FileData->Data, FileData->UploadCompleteEvent, Retry);
  548. if (Retry)
  549. {
  550. AppLogFmt(L"Will retry uploading opened/edited file \"%s\".", (FileData->FileName));
  551. UploadComplete(Index);
  552. FileData->Timestamp = PrevTimestamp;
  553. }
  554. }
  555. catch(...)
  556. {
  557. AppLogFmt(L"Failed to upload opened/edited file \"%s\".", (FileData->FileName));
  558. // upload failed (was not even started)
  559. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  560. {
  561. UploadComplete(Index);
  562. }
  563. throw;
  564. }
  565. }
  566. }
  567. }
  568. }
  569. //---------------------------------------------------------------------------
  570. int __fastcall TEditorManager::FindFile(const TObject * Token)
  571. {
  572. int Index = -1;
  573. for (unsigned int i = 0; i < FFiles.size(); i++)
  574. {
  575. if (!FFiles[i].Closed && (FFiles[i].Token == Token))
  576. {
  577. Index = i;
  578. break;
  579. }
  580. }
  581. return Index;
  582. }
  583. //---------------------------------------------------------------------------
  584. int __fastcall TEditorManager::WaitFor(unsigned int Count, const HANDLE * Handles,
  585. TWaitHandle WaitFor)
  586. {
  587. static const unsigned int Offset = MAXIMUM_WAIT_OBJECTS;
  588. int Result = -1;
  589. unsigned int Start = 0;
  590. while ((Result < 0) && (Start < Count))
  591. {
  592. unsigned int C = (Count - Start > Offset ? Offset : Count - Start);
  593. unsigned int WaitResult = WaitForMultipleObjects(C, Handles + Start, false, 0);
  594. if (WaitResult == WAIT_FAILED)
  595. {
  596. throw Exception(LoadStr(WATCH_ERROR_GENERAL));
  597. }
  598. else if (WaitResult != WAIT_TIMEOUT)
  599. {
  600. // WAIT_OBJECT_0 is zero
  601. DebugAssert(WaitResult < WAIT_OBJECT_0 + Count);
  602. HANDLE Handle = Handles[WaitResult - WAIT_OBJECT_0];
  603. for (unsigned int i = 0; i < FFiles.size(); i++)
  604. {
  605. TFileData * Data = &FFiles[i];
  606. HANDLE FHandle;
  607. switch (WaitFor)
  608. {
  609. case PROCESS:
  610. FHandle = Data->Process;
  611. break;
  612. case EVENT:
  613. FHandle = Data->UploadCompleteEvent;
  614. break;
  615. };
  616. if (FHandle == Handle)
  617. {
  618. Result = Start + i;
  619. break;
  620. }
  621. }
  622. DebugAssert(Result >= 0);
  623. }
  624. Start += C;
  625. }
  626. return Result;
  627. }
  628. //---------------------------------------------------------------------------