123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- //---------------------------------------------------------------------------
- #include <WinPCH.h>
- #pragma hdrstop
- #include <SessionData.h>
- #include "EditorManager.h"
- //---------------------------------------------------------------------------
- TEditedFileData::TEditedFileData()
- {
- ForceText = false;
- Terminal = NULL;
- SessionData = NULL;
- }
- //---------------------------------------------------------------------------
- TEditedFileData::~TEditedFileData()
- {
- delete SessionData;
- }
- //---------------------------------------------------------------------------
- __fastcall TEditorManager::TEditorManager()
- {
- FSection = new TCriticalSection();
- FOnFileChange = NULL;
- FOnFileReload = NULL;
- FOnFileEarlyClosed = NULL;
- FOnFileUploadComplete = NULL;
- }
- //---------------------------------------------------------------------------
- __fastcall TEditorManager::~TEditorManager()
- {
- for (unsigned int i = FFiles.size(); i > 0; i--)
- {
- int Index = i - 1;
- TFileData * FileData = &FFiles[Index];
- // pending should be only external editors and files being uploaded
- DebugAssert(FileData->Closed || FileData->External);
- if (!FileData->Closed)
- {
- if (!CloseFile(Index, true, true))
- {
- ReleaseFile(Index);
- }
- }
- }
- delete FSection;
- }
- //---------------------------------------------------------------------------
- bool __fastcall TEditorManager::Empty(bool IgnoreClosed)
- {
- TGuard Guard(FSection);
- bool Result;
- if (!IgnoreClosed)
- {
- Result = (FFiles.size() == 0);
- }
- else
- {
- Result = true;
- for (unsigned int i = 0; i < FFiles.size(); i++)
- {
- if (!FFiles[i].Closed)
- {
- Result = false;
- break;
- }
- }
- }
- return Result;
- }
- //---------------------------------------------------------------------------
- bool __fastcall TEditorManager::CanAddFile(const UnicodeString RemoteDirectory,
- const UnicodeString OriginalFileName, const UnicodeString SessionName,
- TObject *& Token, UnicodeString & ExistingLocalRootDirectory,
- UnicodeString & ExistingLocalDirectory)
- {
- TGuard Guard(FSection);
- bool Result = true;
- Token = NULL;
- for (unsigned int i = 0; i < FFiles.size(); i++)
- {
- TFileData * FileData = &FFiles[i];
- // include even "closed" (=being uploaded) files as it is nonsense
- // to download file being uploaded
- if ((FileData->Data->RemoteDirectory == RemoteDirectory) &&
- (FileData->Data->OriginalFileName == OriginalFileName) &&
- (FileData->Data->SessionName == SessionName))
- {
- if (!FileData->External)
- {
- Result = false;
- if (!FileData->Closed && (FileData->Token != NULL))
- {
- Token = FileData->Token;
- }
- }
- else
- {
- // MDI editor?
- if (FileData->Process == INVALID_HANDLE_VALUE)
- {
- // file is just being uploaded, do not allow new editor instance
- if (FileData->Closed)
- {
- Result = false;
- }
- else
- {
- UnicodeString AExistingLocalDirectory = ExtractFilePath(FileData->FileName);
- // If the temporary directory was deleted, behave like the file was never opened
- if (DirectoryExists(AExistingLocalDirectory))
- {
- // get directory where the file already is so we download it there again
- ExistingLocalRootDirectory = FileData->Data->LocalRootDirectory;
- ExistingLocalDirectory = AExistingLocalDirectory;
- }
- CloseFile(i, false, false); // do not delete file
- Result = true;
- }
- }
- else
- {
- Result = false;
- }
- }
- break;
- }
- }
- if (Result)
- {
- if (FFiles.size() >= WinConfiguration->Editor.MaxEditors)
- {
- throw Exception(LoadStr(TOO_MANY_EDITORS));
- }
- }
- return Result;
- }
- //---------------------------------------------------------------------------
- TEditedFileData * TEditorManager::FindByUploadCompleteEvent(HANDLE UploadCompleteEvent)
- {
- TGuard Guard(FSection);
- for (unsigned int i = 0; i < FFiles.size(); i++)
- {
- TFileData * FileData = &FFiles[i];
- if (FileData->UploadCompleteEvent == UploadCompleteEvent)
- {
- return FileData->Data;
- }
- }
- return NULL;
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::ProcessFiles(TEditedFileProcessEvent Callback, void * Arg)
- {
- TGuard Guard(FSection);
- for (unsigned int i = 0; i < FFiles.size(); i++)
- {
- TFileData * FileData = &FFiles[i];
- Callback(FileData->FileName, FileData->Data,
- (FileData->Closed ? NULL : FileData->Token), Arg);
- }
- }
- //---------------------------------------------------------------------------
- bool __fastcall TEditorManager::CloseInternalEditors(TNotifyEvent CloseCallback)
- {
- TGuard Guard(FSection);
- // Traverse from end, as closing internal editor causes deletion of
- // respective file vector element.
- TObject * PrevToken = NULL;
- for (unsigned int i = FFiles.size(); i > 0; i--)
- {
- // Note that element may be deleted by external cause (like if external editor
- // is closed while "save confirmation" message is displayed).
- if (i <= FFiles.size())
- {
- TFileData * FileData = &FFiles[i - 1];
- // PrevToken is simple check not to ask same editor twice, however
- // it does not solve all posibilities
- if (!FileData->Closed && (FileData->Token != NULL) &&
- (FileData->Token != PrevToken))
- {
- CloseCallback(FileData->Token);
- }
- }
- }
- bool Result = true;
- for (unsigned int i = 0; i < FFiles.size(); i++)
- {
- TFileData * FileData = &FFiles[i];
- if (!FileData->Closed && (FileData->Token != NULL))
- {
- Result = false;
- break;
- }
- }
- return Result;
- }
- //---------------------------------------------------------------------------
- bool __fastcall TEditorManager::CloseExternalFilesWithoutProcess()
- {
- TGuard Guard(FSection);
- for (unsigned int i = FFiles.size(); i > 0; i--)
- {
- TFileData * FileData = &FFiles[i - 1];
- if (!FileData->Closed && FileData->External &&
- (FileData->Process == INVALID_HANDLE_VALUE))
- {
- CloseFile(i - 1, true, true);
- }
- }
- return true;
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::AddFileInternal(const UnicodeString FileName,
- TEditedFileData * AData, TObject * Token)
- {
- TGuard Guard(FSection);
- std::unique_ptr<TEditedFileData> Data(AData);
- TFileData FileData;
- FileData.FileName = FileName;
- FileData.External = false;
- FileData.Process = INVALID_HANDLE_VALUE;
- FileData.Token = Token;
- AddFile(FileData, Data.release());
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::AddFileExternal(const UnicodeString FileName,
- TEditedFileData * AData, HANDLE Process)
- {
- TGuard Guard(FSection);
- std::unique_ptr<TEditedFileData> Data(AData);
- TFileData FileData;
- FileData.FileName = FileName;
- FileData.External = true;
- FileData.Process = Process;
- FileData.Token = NULL;
- UnicodeString FilePath = ExtractFilePath(FileData.FileName);
- if (Process != INVALID_HANDLE_VALUE)
- {
- FProcesses.push_back(Process);
- }
- AddFile(FileData, Data.release());
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::Check()
- {
- TGuard Guard(FSection);
- int Index;
- for (Index = 0; Index < static_cast<int>(FFiles.size()); Index++)
- {
- TDateTime NewTimestamp;
- if (HasFileChanged(Index, NewTimestamp))
- {
- TDateTime N = NormalizeTimestamp(Now());
- // Let the editor finish writing to the file
- // (first to avoid uploading partially saved file, second
- // because the timestamp may change more than once during saving).
- // WORKAROUND WithinPastMilliSeconds seems buggy that it return true even if NewTimestamp is within future
- if ((NewTimestamp <= N) &&
- !WithinPastMilliSeconds(N, NewTimestamp, GUIConfiguration->KeepUpToDateChangeDelay))
- {
- CheckFileChange(Index, false);
- }
- }
- }
- if (FProcesses.size() > 0)
- {
- do
- {
- Index = WaitFor(FProcesses.size(), &(FProcesses[0]), PROCESS);
- if (Index >= 0)
- {
- try
- {
- CheckFileChange(Index, false);
- }
- __finally
- {
- if (!EarlyClose(Index))
- {
- // CheckFileChange may fail,
- // but we want to close handles anyway
- CloseFile(Index, false, true);
- }
- }
- }
- }
- while ((Index >= 0) && (FProcesses.size() > 0));
- }
- if (FUploadCompleteEvents.size() > 0)
- {
- do
- {
- Index = WaitFor(FUploadCompleteEvents.size(), &(FUploadCompleteEvents[0]),
- EVENT);
- if (Index >= 0)
- {
- UploadComplete(Index, false);
- }
- }
- while ((Index >= 0) && (FUploadCompleteEvents.size() > 0));
- }
- }
- //---------------------------------------------------------------------------
- bool __fastcall TEditorManager::EarlyClose(int Index)
- {
- TFileData * FileData = &FFiles[Index];
- bool Result =
- (FileData->Process != INVALID_HANDLE_VALUE) &&
- (Now() - FileData->Opened <=
- TDateTime(0, 0, static_cast<unsigned short>(WinConfiguration->Editor.EarlyClose), 0)) &&
- (FOnFileEarlyClosed != NULL);
- if (Result)
- {
- Result = false;
- FOnFileEarlyClosed(FileData->Data, Result);
- if (Result)
- {
- // forget the associated process
- CloseProcess(Index);
- }
- }
- return Result;
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::FileChanged(TObject * Token)
- {
- TGuard Guard(FSection);
- int Index = FindFile(Token);
- DebugAssert(Index >= 0);
- DebugAssert(!FFiles[Index].External);
- CheckFileChange(Index, true);
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::FileReload(TObject * Token)
- {
- TGuard Guard(FSection);
- int Index = FindFile(Token);
- DebugAssert(Index >= 0);
- TFileData * FileData = &FFiles[Index];
- DebugAssert(!FileData->External);
- TAutoFlag ReloadingFlag(FileData->Reloading);
- OnFileReload(FileData->FileName, FileData->Data);
- GetFileTimestamp(FileData->FileName, FileData->Timestamp);
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::FileClosed(TObject * Token, bool Forced)
- {
- TGuard Guard(FSection);
- int Index = FindFile(Token);
- DebugAssert(Index >= 0);
- DebugAssert(!FFiles[Index].External);
- CheckFileChange(Index, false);
- CloseFile(Index, false, !Forced);
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::AddFile(TFileData & FileData, TEditedFileData * AData)
- {
- std::unique_ptr<TEditedFileData> Data(AData);
- GetFileTimestamp(FileData.FileName, FileData.Timestamp);
- FileData.Closed = false;
- FileData.UploadCompleteEvent = INVALID_HANDLE_VALUE;
- FileData.Opened = Now();
- FileData.Reupload = false;
- FileData.Reloading = false;
- FileData.Saves = 0;
- FileData.Data = Data.get();
- FFiles.push_back(FileData);
- Data.release(); // ownership passed
- }
- //---------------------------------------------------------------------------
- void TEditorManager::UploadComplete(int Index, bool Retry)
- {
- TFileData * FileData = &FFiles[Index];
- DebugAssert(FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE);
- CloseHandle(FileData->UploadCompleteEvent);
- FUploadCompleteEvents.erase(std::find(FUploadCompleteEvents.begin(),
- FUploadCompleteEvents.end(), FileData->UploadCompleteEvent));
- FileData->UploadCompleteEvent = INVALID_HANDLE_VALUE;
- if (FileData->Closed)
- {
- CloseFile(Index, false, true);
- }
- else
- {
- if (FileData->Reupload)
- {
- FileData->Reupload = false;
- CheckFileChange(Index, true);
- }
- // so far used only to signal to the internal editor that saving was complete,
- // so we do signal once an actual upload is done only
- else if ((FileData->Token != NULL) && (FOnFileUploadComplete != NULL) && !Retry)
- {
- FOnFileUploadComplete(FileData->Token);
- }
- }
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::CloseProcess(int Index)
- {
- TFileData * FileData = &FFiles[Index];
- FProcesses.erase(std::find(FProcesses.begin(), FProcesses.end(), FileData->Process));
- DebugCheck(CloseHandle(FileData->Process));
- FileData->Process = INVALID_HANDLE_VALUE;
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::ReleaseFile(int Index)
- {
- TFileData * FileData = &FFiles[Index];
- delete FileData->Data;
- FileData->Data = NULL;
- }
- //---------------------------------------------------------------------------
- bool __fastcall TEditorManager::CloseFile(int Index, bool IgnoreErrors, bool Delete)
- {
- bool Result = false;
- TFileData * FileData = &FFiles[Index];
- if (FileData->Process != INVALID_HANDLE_VALUE)
- {
- CloseProcess(Index);
- }
- if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
- {
- FileData->Closed = true;
- AppLogFmt(L"Opened/edited file \"%s\" has been closed, but the file is still being uploaded.", (FileData->FileName));
- }
- else
- {
- UnicodeString LocalRootDirectory = FileData->Data->LocalRootDirectory;
- UnicodeString FileName = FileData->FileName; // Before it's released
- ReleaseFile(Index);
- FFiles.erase(FFiles.begin() + Index);
- Result = true;
- if (Delete && !LocalRootDirectory.IsEmpty())
- {
- if (!RecursiveDeleteFile(ExcludeTrailingBackslash(LocalRootDirectory)) &&
- !IgnoreErrors)
- {
- throw Exception(FMTLOAD(DELETE_TEMP_EXECUTE_FILE_ERROR, (LocalRootDirectory)));
- }
- AppLogFmt(L"Deleted opened/edited file [%s] folder \"%s\".", (FileName, LocalRootDirectory));
- }
- else
- {
- AppLogFmt(L"Opened/edited file \"%s\" has been closed.", (FileName));
- }
- }
- return Result;
- }
- //---------------------------------------------------------------------------
- TDateTime TEditorManager::NormalizeTimestamp(const TDateTime & Timestamp)
- {
- return TTimeZone::Local->ToUniversalTime(Timestamp);
- }
- //---------------------------------------------------------------------------
- bool TEditorManager::GetFileTimestamp(const UnicodeString & FileName, TDateTime & Timestamp)
- {
- TSearchRecSmart ASearchRec;
- bool Result = FileSearchRec(FileName, ASearchRec);
- if (Result)
- {
- Timestamp = NormalizeTimestamp(ASearchRec.GetLastWriteTime());
- }
- return Result;
- }
- //---------------------------------------------------------------------------
- bool __fastcall TEditorManager::HasFileChanged(int Index, TDateTime & NewTimestamp)
- {
- TFileData * FileData = &FFiles[Index];
- bool Result;
- if (FileData->Reloading)
- {
- Result = false;
- }
- else
- {
- Result =
- GetFileTimestamp(FileData->FileName, NewTimestamp) &&
- (FileData->Timestamp != NewTimestamp);
- }
- return Result;
- }
- //---------------------------------------------------------------------------
- void __fastcall TEditorManager::CheckFileChange(int Index, bool Force)
- {
- TDateTime NewTimestamp;
- bool Changed = HasFileChanged(Index, NewTimestamp);
- if (Force || Changed)
- {
- TFileData * FileData = &FFiles[Index];
- if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
- {
- AppLogFmt(L"Opened/edited file \"%s\" was changed, but it still being uploaded.", (FileData->FileName));
- FileData->Reupload = true;
- }
- else
- {
- if (Changed)
- {
- AppLogFmt(L"Opened/edited file \"%s\" was changed.", (FileData->FileName));
- }
- else
- {
- AppLogFmt(L"Forcing upload of opened/edited file \"%s\".", (FileData->FileName));
- }
- bool Upload = true;
- if (!Force)
- {
- HANDLE Handle = CreateFile(ApiPath(FileData->FileName).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
- if (Handle == INVALID_HANDLE_VALUE)
- {
- int Error = GetLastError();
- AppLogFmt(L"Opened/edited file \"%s\" is locked, delaying upload.", (FileData->FileName));
- if (Error == ERROR_ACCESS_DENIED)
- {
- Upload = false;
- }
- }
- else
- {
- CloseHandle(Handle);
- }
- }
- if (Upload)
- {
- FileData->UploadCompleteEvent = CreateEvent(NULL, false, false, NULL);
- FUploadCompleteEvents.push_back(FileData->UploadCompleteEvent);
- TDateTime PrevTimestamp = FileData->Timestamp;
- FileData->Timestamp = NewTimestamp;
- FileData->Saves++;
- if (FileData->Saves == 1)
- {
- Configuration->Usage->Inc(L"RemoteFilesSaved");
- }
- Configuration->Usage->Inc(L"RemoteFileSaves");
- try
- {
- DebugAssert(OnFileChange != NULL);
- bool Retry = false;
- AppLogFmt(L"Uploading opened/edited file \"%s\".", (FileData->FileName));
- OnFileChange(FileData->FileName, FileData->Timestamp, FileData->Data, FileData->UploadCompleteEvent, Retry);
- if (Retry)
- {
- AppLogFmt(L"Will retry uploading opened/edited file \"%s\".", (FileData->FileName));
- UploadComplete(Index, true);
- FileData->Timestamp = PrevTimestamp;
- }
- }
- catch(...)
- {
- AppLogFmt(L"Failed to upload opened/edited file \"%s\".", (FileData->FileName));
- // upload failed (was not even started)
- if (FileData->UploadCompleteEvent != INVALID_HANDLE_VALUE)
- {
- UploadComplete(Index, false);
- }
- throw;
- }
- }
- }
- }
- }
- //---------------------------------------------------------------------------
- int __fastcall TEditorManager::FindFile(const TObject * Token)
- {
- int Index = -1;
- for (unsigned int i = 0; i < FFiles.size(); i++)
- {
- if (!FFiles[i].Closed && (FFiles[i].Token == Token))
- {
- Index = i;
- break;
- }
- }
- return Index;
- }
- //---------------------------------------------------------------------------
- int __fastcall TEditorManager::WaitFor(unsigned int Count, const HANDLE * Handles,
- TWaitHandle WaitFor)
- {
- static const unsigned int Offset = MAXIMUM_WAIT_OBJECTS;
- int Result = -1;
- unsigned int Start = 0;
- while ((Result < 0) && (Start < Count))
- {
- unsigned int C = (Count - Start > Offset ? Offset : Count - Start);
- unsigned int WaitResult = WaitForMultipleObjects(C, Handles + Start, false, 0);
- if (WaitResult == WAIT_FAILED)
- {
- throw Exception(LoadStr(WATCH_ERROR_GENERAL));
- }
- else if (WaitResult != WAIT_TIMEOUT)
- {
- // WAIT_OBJECT_0 is zero
- DebugAssert(WaitResult < WAIT_OBJECT_0 + Count);
- HANDLE Handle = Handles[WaitResult - WAIT_OBJECT_0];
- for (unsigned int i = 0; i < FFiles.size(); i++)
- {
- TFileData * Data = &FFiles[i];
- HANDLE FHandle;
- switch (WaitFor)
- {
- case PROCESS:
- FHandle = Data->Process;
- break;
- case EVENT:
- FHandle = Data->UploadCompleteEvent;
- break;
- };
- if (FHandle == Handle)
- {
- Result = Start + i;
- break;
- }
- }
- DebugAssert(Result >= 0);
- }
- Start += C;
- }
- return Result;
- }
- //---------------------------------------------------------------------------
|