EditorManager.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571
  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. OnFileReload(FileData->FileName, FileData->Data);
  319. FileAge(FileData->FileName, FileData->Timestamp);
  320. }
  321. //---------------------------------------------------------------------------
  322. void __fastcall TEditorManager::FileClosed(TObject * Token, bool Forced)
  323. {
  324. int Index = FindFile(Token);
  325. DebugAssert(Index >= 0);
  326. DebugAssert(!FFiles[Index].External);
  327. CheckFileChange(Index, false);
  328. CloseFile(Index, false, !Forced);
  329. }
  330. //---------------------------------------------------------------------------
  331. void __fastcall TEditorManager::AddFile(TFileData & FileData, TEditedFileData * AData)
  332. {
  333. std::unique_ptr<TEditedFileData> Data(AData);
  334. FileAge(FileData.FileName, FileData.Timestamp);
  335. FileData.Closed = false;
  336. FileData.UploadCompleteEvent = INVALID_HANDLE_VALUE;
  337. FileData.Opened = Now();
  338. FileData.Reupload = false;
  339. FileData.Saves = 0;
  340. FileData.Data = Data.get();
  341. FFiles.push_back(FileData);
  342. Data.release(); // ownership passed
  343. }
  344. //---------------------------------------------------------------------------
  345. void __fastcall TEditorManager::UploadComplete(int Index)
  346. {
  347. TFileData * FileData = &FFiles[Index];
  348. DebugAssert(FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE);
  349. CloseHandle(FileData->UploadCompleteEvent);
  350. FUploadCompleteEvents.erase(std::find(FUploadCompleteEvents.begin(),
  351. FUploadCompleteEvents.end(), FileData->UploadCompleteEvent));
  352. FileData->UploadCompleteEvent = INVALID_HANDLE_VALUE;
  353. if (FileData->Closed)
  354. {
  355. CloseFile(Index, false, true);
  356. }
  357. else
  358. {
  359. if (FileData->Reupload)
  360. {
  361. FileData->Reupload = false;
  362. CheckFileChange(Index, true);
  363. }
  364. else if ((FileData->Token != NULL) && (FOnFileUploadComplete != NULL))
  365. {
  366. FOnFileUploadComplete(FileData->Token);
  367. }
  368. }
  369. }
  370. //---------------------------------------------------------------------------
  371. void __fastcall TEditorManager::CloseProcess(int Index)
  372. {
  373. TFileData * FileData = &FFiles[Index];
  374. FProcesses.erase(std::find(FProcesses.begin(), FProcesses.end(), FileData->Process));
  375. DebugCheck(CloseHandle(FileData->Process));
  376. FileData->Process = INVALID_HANDLE_VALUE;
  377. }
  378. //---------------------------------------------------------------------------
  379. void __fastcall TEditorManager::ReleaseFile(int Index)
  380. {
  381. TFileData * FileData = &FFiles[Index];
  382. delete FileData->Data;
  383. FileData->Data = NULL;
  384. }
  385. //---------------------------------------------------------------------------
  386. bool __fastcall TEditorManager::CloseFile(int Index, bool IgnoreErrors, bool Delete)
  387. {
  388. bool Result = false;
  389. TFileData * FileData = &FFiles[Index];
  390. if (FileData->Process != INVALID_HANDLE_VALUE)
  391. {
  392. CloseProcess(Index);
  393. }
  394. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  395. {
  396. FileData->Closed = true;
  397. }
  398. else
  399. {
  400. UnicodeString FileName = FileData->FileName;
  401. UnicodeString LocalRootDirectory = FileData->Data->LocalRootDirectory;
  402. ReleaseFile(Index);
  403. FFiles.erase(FFiles.begin() + Index);
  404. Result = true;
  405. if (Delete)
  406. {
  407. if (!RecursiveDeleteFile(ExcludeTrailingBackslash(LocalRootDirectory), false) &&
  408. !IgnoreErrors)
  409. {
  410. throw Exception(FMTLOAD(DELETE_TEMP_EXECUTE_FILE_ERROR, (LocalRootDirectory)));
  411. }
  412. }
  413. }
  414. return Result;
  415. }
  416. //---------------------------------------------------------------------------
  417. bool __fastcall TEditorManager::HasFileChanged(int Index, TDateTime & NewTimestamp)
  418. {
  419. TFileData * FileData = &FFiles[Index];
  420. FileAge(FileData->FileName, NewTimestamp);
  421. return (FileData->Timestamp != NewTimestamp);
  422. }
  423. //---------------------------------------------------------------------------
  424. void __fastcall TEditorManager::CheckFileChange(int Index, bool Force)
  425. {
  426. TDateTime NewTimestamp;
  427. bool Changed = HasFileChanged(Index, NewTimestamp);
  428. if (Force || Changed)
  429. {
  430. TFileData * FileData = &FFiles[Index];
  431. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  432. {
  433. FileData->Reupload = true;
  434. }
  435. else
  436. {
  437. FileData->UploadCompleteEvent = CreateEvent(NULL, false, false, NULL);
  438. FUploadCompleteEvents.push_back(FileData->UploadCompleteEvent);
  439. FileData->Timestamp = NewTimestamp;
  440. FileData->Saves++;
  441. if (FileData->Saves == 1)
  442. {
  443. Configuration->Usage->Inc(L"RemoteFilesSaved");
  444. }
  445. Configuration->Usage->Inc(L"RemoteFileSaves");
  446. try
  447. {
  448. DebugAssert(OnFileChange != NULL);
  449. OnFileChange(FileData->FileName, FileData->Data,
  450. FileData->UploadCompleteEvent);
  451. }
  452. catch(...)
  453. {
  454. // upload failed (was not even started)
  455. if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
  456. {
  457. UploadComplete(Index);
  458. }
  459. throw;
  460. }
  461. }
  462. }
  463. }
  464. //---------------------------------------------------------------------------
  465. int __fastcall TEditorManager::FindFile(const TObject * Token)
  466. {
  467. int Index = -1;
  468. for (unsigned int i = 0; i < FFiles.size(); i++)
  469. {
  470. if (!FFiles[i].Closed && (FFiles[i].Token == Token))
  471. {
  472. Index = i;
  473. break;
  474. }
  475. }
  476. return Index;
  477. }
  478. //---------------------------------------------------------------------------
  479. int __fastcall TEditorManager::WaitFor(unsigned int Count, const HANDLE * Handles,
  480. TWaitHandle WaitFor)
  481. {
  482. static const unsigned int Offset = MAXIMUM_WAIT_OBJECTS;
  483. int Result = -1;
  484. unsigned int Start = 0;
  485. while ((Result < 0) && (Start < Count))
  486. {
  487. unsigned int C = (Count - Start > Offset ? Offset : Count - Start);
  488. unsigned int WaitResult = WaitForMultipleObjects(C, Handles + Start, false, 0);
  489. if (WaitResult == WAIT_FAILED)
  490. {
  491. throw Exception(LoadStr(WATCH_ERROR_GENERAL));
  492. }
  493. else if (WaitResult != WAIT_TIMEOUT)
  494. {
  495. // WAIT_OBJECT_0 is zero
  496. DebugAssert(WaitResult < WAIT_OBJECT_0 + Count);
  497. HANDLE Handle = Handles[WaitResult - WAIT_OBJECT_0];
  498. for (unsigned int i = 0; i < FFiles.size(); i++)
  499. {
  500. TFileData * Data = &FFiles[i];
  501. HANDLE FHandle;
  502. switch (WaitFor)
  503. {
  504. case PROCESS:
  505. FHandle = Data->Process;
  506. break;
  507. case EVENT:
  508. FHandle = Data->UploadCompleteEvent;
  509. break;
  510. };
  511. if (FHandle == Handle)
  512. {
  513. Result = Start + i;
  514. break;
  515. }
  516. }
  517. DebugAssert(Result >= 0);
  518. }
  519. Start += C;
  520. }
  521. return Result;
  522. }
  523. //---------------------------------------------------------------------------