EditorManager.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  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. // get directory where the file already is so we download it there again
  113. ExistingLocalRootDirectory = FileData->Data->LocalRootDirectory;
  114. ExistingLocalDirectory = ExtractFilePath(FileData->FileName);
  115. CloseFile(i, false, false); // do not delete file
  116. Result = true;
  117. }
  118. }
  119. else
  120. {
  121. Result = false;
  122. }
  123. }
  124. break;
  125. }
  126. }
  127. if (Result)
  128. {
  129. if (FFiles.size() >= WinConfiguration->Editor.MaxEditors)
  130. {
  131. throw Exception(LoadStr(TOO_MANY_EDITORS));
  132. }
  133. }
  134. return Result;
  135. }
  136. //---------------------------------------------------------------------------
  137. void __fastcall TEditorManager::ProcessFiles(TEditedFileProcessEvent Callback, void * Arg)
  138. {
  139. for (unsigned int i = 0; i < FFiles.size(); i++)
  140. {
  141. TFileData * FileData = &FFiles[i];
  142. Callback(FileData->FileName, FileData->Data,
  143. (FileData->Closed ? NULL : FileData->Token), Arg);
  144. }
  145. }
  146. //---------------------------------------------------------------------------
  147. bool __fastcall TEditorManager::CloseInternalEditors(TNotifyEvent CloseCallback)
  148. {
  149. // Traverse from end, as closing internal editor causes deletion of
  150. // respective file vector element.
  151. TObject * PrevToken = NULL;
  152. for (unsigned int i = FFiles.size(); i > 0; i--)
  153. {
  154. // Note that element may be deleted by external cause (like if external editor
  155. // is closed while "save confirmation" message is displayed).
  156. if (i <= FFiles.size())
  157. {
  158. TFileData * FileData = &FFiles[i - 1];
  159. // PrevToken is simple check not to ask same editor twice, however
  160. // it does not solve all posibilities
  161. if (!FileData->Closed && (FileData->Token != NULL) &&
  162. (FileData->Token != PrevToken))
  163. {
  164. CloseCallback(FileData->Token);
  165. }
  166. }
  167. }
  168. bool Result = true;
  169. for (unsigned int i = 0; i < FFiles.size(); i++)
  170. {
  171. TFileData * FileData = &FFiles[i];
  172. if (!FileData->Closed && (FileData->Token != NULL))
  173. {
  174. Result = false;
  175. break;
  176. }
  177. }
  178. return Result;
  179. }
  180. //---------------------------------------------------------------------------
  181. bool __fastcall TEditorManager::CloseExternalFilesWithoutProcess()
  182. {
  183. for (unsigned int i = FFiles.size(); i > 0; i--)
  184. {
  185. TFileData * FileData = &FFiles[i - 1];
  186. if (!FileData->Closed && FileData->External &&
  187. (FileData->Process == INVALID_HANDLE_VALUE))
  188. {
  189. CloseFile(i - 1, true, true);
  190. }
  191. }
  192. return true;
  193. }
  194. //---------------------------------------------------------------------------
  195. void __fastcall TEditorManager::AddFileInternal(const UnicodeString FileName,
  196. TEditedFileData * AData, TObject * Token)
  197. {
  198. std::unique_ptr<TEditedFileData> Data(AData);
  199. TFileData FileData;
  200. FileData.FileName = FileName;
  201. FileData.External = false;
  202. FileData.Process = INVALID_HANDLE_VALUE;
  203. FileData.Token = Token;
  204. AddFile(FileData, Data.release());
  205. }
  206. //---------------------------------------------------------------------------
  207. void __fastcall TEditorManager::AddFileExternal(const UnicodeString FileName,
  208. TEditedFileData * AData, HANDLE Process)
  209. {
  210. std::unique_ptr<TEditedFileData> Data(AData);
  211. TFileData FileData;
  212. FileData.FileName = FileName;
  213. FileData.External = true;
  214. FileData.Process = Process;
  215. FileData.Token = NULL;
  216. UnicodeString FilePath = ExtractFilePath(FileData.FileName);
  217. if (Process != INVALID_HANDLE_VALUE)
  218. {
  219. FProcesses.push_back(Process);
  220. }
  221. AddFile(FileData, Data.release());
  222. }
  223. //---------------------------------------------------------------------------
  224. void __fastcall TEditorManager::Check()
  225. {
  226. int Index;
  227. for (Index = 0; Index < static_cast<int>(FFiles.size()); Index++)
  228. {
  229. TDateTime NewTimestamp;
  230. if (HasFileChanged(Index, NewTimestamp))
  231. {
  232. TDateTime N = Now();
  233. // Let the editor finish writing to the file
  234. // (first to avoid uploading partially saved file, second
  235. // because the timestamp may change more than once during saving).
  236. // WORKAROUND WithinPastMilliSeconds seems buggy that it return true even if NewTimestamp is within future
  237. if ((NewTimestamp <= N) &&
  238. !WithinPastMilliSeconds(N, NewTimestamp, GUIConfiguration->KeepUpToDateChangeDelay))
  239. {
  240. CheckFileChange(Index, false);
  241. }
  242. }
  243. }
  244. if (FProcesses.size() > 0)
  245. {
  246. do
  247. {
  248. Index = WaitFor(FProcesses.size(), &(FProcesses[0]), PROCESS);
  249. if (Index >= 0)
  250. {
  251. try
  252. {
  253. CheckFileChange(Index, false);
  254. }
  255. __finally
  256. {
  257. if (!EarlyClose(Index))
  258. {
  259. // CheckFileChange may fail,
  260. // but we want to close handles anyway
  261. CloseFile(Index, false, true);
  262. }
  263. }
  264. }
  265. }
  266. while ((Index >= 0) && (FProcesses.size() > 0));
  267. }
  268. if (FUploadCompleteEvents.size() > 0)
  269. {
  270. do
  271. {
  272. Index = WaitFor(FUploadCompleteEvents.size(), &(FUploadCompleteEvents[0]),
  273. EVENT);
  274. if (Index >= 0)
  275. {
  276. UploadComplete(Index);
  277. }
  278. }
  279. while ((Index >= 0) && (FUploadCompleteEvents.size() > 0));
  280. }
  281. }
  282. //---------------------------------------------------------------------------
  283. bool __fastcall TEditorManager::EarlyClose(int Index)
  284. {
  285. TFileData * FileData = &FFiles[Index];
  286. bool Result =
  287. (FileData->Process != INVALID_HANDLE_VALUE) &&
  288. (Now() - FileData->Opened <=
  289. TDateTime(0, 0, static_cast<unsigned short>(WinConfiguration->Editor.EarlyClose), 0)) &&
  290. (FOnFileEarlyClosed != NULL);
  291. if (Result)
  292. {
  293. Result = false;
  294. FOnFileEarlyClosed(FileData->Data, Result);
  295. if (Result)
  296. {
  297. // forget the associated process
  298. CloseProcess(Index);
  299. }
  300. }
  301. return Result;
  302. }
  303. //---------------------------------------------------------------------------
  304. void __fastcall TEditorManager::FileChanged(TObject * Token)
  305. {
  306. int Index = FindFile(Token);
  307. DebugAssert(Index >= 0);
  308. DebugAssert(!FFiles[Index].External);
  309. CheckFileChange(Index, true);
  310. }
  311. //---------------------------------------------------------------------------
  312. void __fastcall TEditorManager::FileReload(TObject * Token)
  313. {
  314. int Index = FindFile(Token);
  315. DebugAssert(Index >= 0);
  316. TFileData * FileData = &FFiles[Index];
  317. DebugAssert(!FileData->External);
  318. TAutoFlag ReloadingFlag(FileData->Reloading);
  319. OnFileReload(FileData->FileName, FileData->Data);
  320. FileAge(FileData->FileName, FileData->Timestamp);
  321. }
  322. //---------------------------------------------------------------------------
  323. void __fastcall TEditorManager::FileClosed(TObject * Token, bool Forced)
  324. {
  325. int Index = FindFile(Token);
  326. DebugAssert(Index >= 0);
  327. DebugAssert(!FFiles[Index].External);
  328. CheckFileChange(Index, false);
  329. CloseFile(Index, false, !Forced);
  330. }
  331. //---------------------------------------------------------------------------
  332. void __fastcall TEditorManager::AddFile(TFileData & FileData, TEditedFileData * AData)
  333. {
  334. std::unique_ptr<TEditedFileData> Data(AData);
  335. FileAge(FileData.FileName, FileData.Timestamp);
  336. FileData.Closed = false;
  337. FileData.UploadCompleteEvent = INVALID_HANDLE_VALUE;
  338. FileData.Opened = Now();
  339. FileData.Reupload = false;
  340. FileData.Reloading = false;
  341. FileData.Saves = 0;
  342. FileData.Data = Data.get();
  343. FFiles.push_back(FileData);
  344. Data.release(); // ownership passed
  345. }
  346. //---------------------------------------------------------------------------
  347. void __fastcall TEditorManager::UploadComplete(int Index)
  348. {
  349. TFileData * FileData = &FFiles[Index];
  350. DebugAssert(FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE);
  351. CloseHandle(FileData->UploadCompleteEvent);
  352. FUploadCompleteEvents.erase(std::find(FUploadCompleteEvents.begin(),
  353. FUploadCompleteEvents.end(), FileData->UploadCompleteEvent));
  354. FileData->UploadCompleteEvent = INVALID_HANDLE_VALUE;
  355. if (FileData->Closed)
  356. {
  357. CloseFile(Index, false, true);
  358. }
  359. else
  360. {
  361. if (FileData->Reupload)
  362. {
  363. FileData->Reupload = false;
  364. CheckFileChange(Index, true);
  365. }
  366. else if ((FileData->Token != NULL) && (FOnFileUploadComplete != NULL))
  367. {
  368. FOnFileUploadComplete(FileData->Token);
  369. }
  370. }
  371. }
  372. //---------------------------------------------------------------------------
  373. void __fastcall TEditorManager::CloseProcess(int Index)
  374. {
  375. TFileData * FileData = &FFiles[Index];
  376. FProcesses.erase(std::find(FProcesses.begin(), FProcesses.end(), FileData->Process));
  377. DebugCheck(CloseHandle(FileData->Process));
  378. FileData->Process = INVALID_HANDLE_VALUE;
  379. }
  380. //---------------------------------------------------------------------------
  381. void __fastcall TEditorManager::ReleaseFile(int Index)
  382. {
  383. TFileData * FileData = &FFiles[Index];
  384. delete FileData->Data;
  385. FileData->Data = NULL;
  386. }
  387. //---------------------------------------------------------------------------
  388. bool __fastcall TEditorManager::CloseFile(int Index, bool IgnoreErrors, bool Delete)
  389. {
  390. bool Result = false;
  391. TFileData * FileData = &FFiles[Index];
  392. if (FileData->Process != INVALID_HANDLE_VALUE)
  393. {
  394. CloseProcess(Index);
  395. }
  396. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  397. {
  398. FileData->Closed = true;
  399. }
  400. else
  401. {
  402. UnicodeString FileName = FileData->FileName;
  403. UnicodeString LocalRootDirectory = FileData->Data->LocalRootDirectory;
  404. ReleaseFile(Index);
  405. FFiles.erase(FFiles.begin() + Index);
  406. Result = true;
  407. if (Delete && !LocalRootDirectory.IsEmpty())
  408. {
  409. if (!RecursiveDeleteFile(ExcludeTrailingBackslash(LocalRootDirectory), false) &&
  410. !IgnoreErrors)
  411. {
  412. throw Exception(FMTLOAD(DELETE_TEMP_EXECUTE_FILE_ERROR, (LocalRootDirectory)));
  413. }
  414. }
  415. }
  416. return Result;
  417. }
  418. //---------------------------------------------------------------------------
  419. bool __fastcall TEditorManager::HasFileChanged(int Index, TDateTime & NewTimestamp)
  420. {
  421. TFileData * FileData = &FFiles[Index];
  422. bool Result;
  423. if (FileData->Reloading)
  424. {
  425. Result = false;
  426. }
  427. else
  428. {
  429. FileAge(FileData->FileName, NewTimestamp);
  430. Result = (FileData->Timestamp != NewTimestamp);
  431. }
  432. return Result;
  433. }
  434. //---------------------------------------------------------------------------
  435. void __fastcall TEditorManager::CheckFileChange(int Index, bool Force)
  436. {
  437. TDateTime NewTimestamp;
  438. bool Changed = HasFileChanged(Index, NewTimestamp);
  439. if (Force || Changed)
  440. {
  441. TFileData * FileData = &FFiles[Index];
  442. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  443. {
  444. FileData->Reupload = true;
  445. }
  446. else
  447. {
  448. FileData->UploadCompleteEvent = CreateEvent(NULL, false, false, NULL);
  449. FUploadCompleteEvents.push_back(FileData->UploadCompleteEvent);
  450. FileData->Timestamp = NewTimestamp;
  451. FileData->Saves++;
  452. if (FileData->Saves == 1)
  453. {
  454. Configuration->Usage->Inc(L"RemoteFilesSaved");
  455. }
  456. Configuration->Usage->Inc(L"RemoteFileSaves");
  457. try
  458. {
  459. DebugAssert(OnFileChange != NULL);
  460. OnFileChange(FileData->FileName, FileData->Data,
  461. FileData->UploadCompleteEvent);
  462. }
  463. catch(...)
  464. {
  465. // upload failed (was not even started)
  466. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  467. {
  468. UploadComplete(Index);
  469. }
  470. throw;
  471. }
  472. }
  473. }
  474. }
  475. //---------------------------------------------------------------------------
  476. int __fastcall TEditorManager::FindFile(const TObject * Token)
  477. {
  478. int Index = -1;
  479. for (unsigned int i = 0; i < FFiles.size(); i++)
  480. {
  481. if (!FFiles[i].Closed && (FFiles[i].Token == Token))
  482. {
  483. Index = i;
  484. break;
  485. }
  486. }
  487. return Index;
  488. }
  489. //---------------------------------------------------------------------------
  490. int __fastcall TEditorManager::WaitFor(unsigned int Count, const HANDLE * Handles,
  491. TWaitHandle WaitFor)
  492. {
  493. static const unsigned int Offset = MAXIMUM_WAIT_OBJECTS;
  494. int Result = -1;
  495. unsigned int Start = 0;
  496. while ((Result < 0) && (Start < Count))
  497. {
  498. unsigned int C = (Count - Start > Offset ? Offset : Count - Start);
  499. unsigned int WaitResult = WaitForMultipleObjects(C, Handles + Start, false, 0);
  500. if (WaitResult == WAIT_FAILED)
  501. {
  502. throw Exception(LoadStr(WATCH_ERROR_GENERAL));
  503. }
  504. else if (WaitResult != WAIT_TIMEOUT)
  505. {
  506. // WAIT_OBJECT_0 is zero
  507. DebugAssert(WaitResult < WAIT_OBJECT_0 + Count);
  508. HANDLE Handle = Handles[WaitResult - WAIT_OBJECT_0];
  509. for (unsigned int i = 0; i < FFiles.size(); i++)
  510. {
  511. TFileData * Data = &FFiles[i];
  512. HANDLE FHandle;
  513. switch (WaitFor)
  514. {
  515. case PROCESS:
  516. FHandle = Data->Process;
  517. break;
  518. case EVENT:
  519. FHandle = Data->UploadCompleteEvent;
  520. break;
  521. };
  522. if (FHandle == Handle)
  523. {
  524. Result = Start + i;
  525. break;
  526. }
  527. }
  528. DebugAssert(Result >= 0);
  529. }
  530. Start += C;
  531. }
  532. return Result;
  533. }
  534. //---------------------------------------------------------------------------