EditorManager.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635
  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);
  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)
  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 ((FileData->Token != NULL) && (FOnFileUploadComplete != NULL))
  372. {
  373. FOnFileUploadComplete(FileData->Data, FileData->Token);
  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. ReleaseFile(Index);
  410. FFiles.erase(FFiles.begin() + Index);
  411. Result = true;
  412. if (Delete && !LocalRootDirectory.IsEmpty())
  413. {
  414. if (!RecursiveDeleteFile(ExcludeTrailingBackslash(LocalRootDirectory), false) &&
  415. !IgnoreErrors)
  416. {
  417. throw Exception(FMTLOAD(DELETE_TEMP_EXECUTE_FILE_ERROR, (LocalRootDirectory)));
  418. }
  419. AppLogFmt(L"Deleted opened/edited file [%s] folder \"%s\".", (FileData->FileName, LocalRootDirectory));
  420. }
  421. else
  422. {
  423. AppLogFmt(L"Opened/edited file \"%s\" has been closed.", (FileData->FileName));
  424. }
  425. }
  426. return Result;
  427. }
  428. //---------------------------------------------------------------------------
  429. TDateTime TEditorManager::NormalizeTimestamp(const TDateTime & Timestamp)
  430. {
  431. return TTimeZone::Local->ToUniversalTime(Timestamp);
  432. }
  433. //---------------------------------------------------------------------------
  434. bool TEditorManager::GetFileTimestamp(const UnicodeString & FileName, TDateTime & Timestamp)
  435. {
  436. TSearchRecSmart ASearchRec;
  437. bool Result = FileSearchRec(FileName, ASearchRec);
  438. if (Result)
  439. {
  440. Timestamp = NormalizeTimestamp(ASearchRec.GetLastWriteTime());
  441. }
  442. return Result;
  443. }
  444. //---------------------------------------------------------------------------
  445. bool __fastcall TEditorManager::HasFileChanged(int Index, TDateTime & NewTimestamp)
  446. {
  447. TFileData * FileData = &FFiles[Index];
  448. bool Result;
  449. if (FileData->Reloading)
  450. {
  451. Result = false;
  452. }
  453. else
  454. {
  455. Result =
  456. GetFileTimestamp(FileData->FileName, NewTimestamp) &&
  457. (FileData->Timestamp != NewTimestamp);
  458. }
  459. return Result;
  460. }
  461. //---------------------------------------------------------------------------
  462. void __fastcall TEditorManager::CheckFileChange(int Index, bool Force)
  463. {
  464. TDateTime NewTimestamp;
  465. bool Changed = HasFileChanged(Index, NewTimestamp);
  466. if (Force || Changed)
  467. {
  468. TFileData * FileData = &FFiles[Index];
  469. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  470. {
  471. FileData->Reupload = true;
  472. }
  473. else
  474. {
  475. bool Upload = true;
  476. if (!Force)
  477. {
  478. HANDLE Handle = CreateFile(ApiPath(FileData->FileName).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
  479. if (Handle == INVALID_HANDLE_VALUE)
  480. {
  481. int Error = GetLastError();
  482. if (Error == ERROR_ACCESS_DENIED)
  483. {
  484. Upload = false;
  485. }
  486. }
  487. else
  488. {
  489. CloseHandle(Handle);
  490. }
  491. }
  492. if (Upload)
  493. {
  494. FileData->UploadCompleteEvent = CreateEvent(NULL, false, false, NULL);
  495. FUploadCompleteEvents.push_back(FileData->UploadCompleteEvent);
  496. TDateTime PrevTimestamp = FileData->Timestamp;
  497. FileData->Timestamp = NewTimestamp;
  498. FileData->Saves++;
  499. if (FileData->Saves == 1)
  500. {
  501. Configuration->Usage->Inc(L"RemoteFilesSaved");
  502. }
  503. Configuration->Usage->Inc(L"RemoteFileSaves");
  504. try
  505. {
  506. DebugAssert(OnFileChange != NULL);
  507. bool Retry = false;
  508. OnFileChange(FileData->FileName, FileData->Data, FileData->UploadCompleteEvent, Retry);
  509. if (Retry)
  510. {
  511. UploadComplete(Index);
  512. FileData->Timestamp = PrevTimestamp;
  513. }
  514. }
  515. catch(...)
  516. {
  517. // upload failed (was not even started)
  518. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  519. {
  520. UploadComplete(Index);
  521. }
  522. throw;
  523. }
  524. }
  525. }
  526. }
  527. }
  528. //---------------------------------------------------------------------------
  529. int __fastcall TEditorManager::FindFile(const TObject * Token)
  530. {
  531. int Index = -1;
  532. for (unsigned int i = 0; i < FFiles.size(); i++)
  533. {
  534. if (!FFiles[i].Closed && (FFiles[i].Token == Token))
  535. {
  536. Index = i;
  537. break;
  538. }
  539. }
  540. return Index;
  541. }
  542. //---------------------------------------------------------------------------
  543. int __fastcall TEditorManager::WaitFor(unsigned int Count, const HANDLE * Handles,
  544. TWaitHandle WaitFor)
  545. {
  546. static const unsigned int Offset = MAXIMUM_WAIT_OBJECTS;
  547. int Result = -1;
  548. unsigned int Start = 0;
  549. while ((Result < 0) && (Start < Count))
  550. {
  551. unsigned int C = (Count - Start > Offset ? Offset : Count - Start);
  552. unsigned int WaitResult = WaitForMultipleObjects(C, Handles + Start, false, 0);
  553. if (WaitResult == WAIT_FAILED)
  554. {
  555. throw Exception(LoadStr(WATCH_ERROR_GENERAL));
  556. }
  557. else if (WaitResult != WAIT_TIMEOUT)
  558. {
  559. // WAIT_OBJECT_0 is zero
  560. DebugAssert(WaitResult < WAIT_OBJECT_0 + Count);
  561. HANDLE Handle = Handles[WaitResult - WAIT_OBJECT_0];
  562. for (unsigned int i = 0; i < FFiles.size(); i++)
  563. {
  564. TFileData * Data = &FFiles[i];
  565. HANDLE FHandle;
  566. switch (WaitFor)
  567. {
  568. case PROCESS:
  569. FHandle = Data->Process;
  570. break;
  571. case EVENT:
  572. FHandle = Data->UploadCompleteEvent;
  573. break;
  574. };
  575. if (FHandle == Handle)
  576. {
  577. Result = Start + i;
  578. break;
  579. }
  580. }
  581. DebugAssert(Result >= 0);
  582. }
  583. Start += C;
  584. }
  585. return Result;
  586. }
  587. //---------------------------------------------------------------------------