EditorManager.cpp 18 KB

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