| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453 |
- #include "stdafx.h"
- #include "ClipEditThread.h"
- #include "Options.h"
- #include "Misc.h"
- #include "Clip.h"
- #include "CP_Main.h"
- #include "ConvertRTFToText.h"
- #include "..\Shared\TextConvert.h"
- #define EVENT_FILE_CHANGED 1
- #define MAX_TIMEOUT 86400
- CClipEditThread::CClipEditThread()
- {
- m_folderHandle = INVALID_HANDLE_VALUE;
- m_threadName = _T("ClipEditTrackingThread");
- m_waitTimeout = MAX_TIMEOUT;
- }
- CClipEditThread::~CClipEditThread()
- {
- Close();
- }
- void CClipEditThread::Close()
- {
- Stop();
-
- if (m_folderHandle != INVALID_HANDLE_VALUE)
- {
- CloseHandle(m_folderHandle);
- m_folderHandle = INVALID_HANDLE_VALUE;
- }
- RemoveEvent(EVENT_FILE_CHANGED);
- m_overlapped.hEvent = INVALID_HANDLE_VALUE;
- CString editClipFolder = CGetSetOptions::GetPath(PATH_EDIT_CLIPS);
- DeleteFolderFiles(editClipFolder, TRUE, CTimeSpan(7, 0, 0, 0));
- }
- void CClipEditThread::StartWatchingFolderForChanges()
- {
- CString editClipFolder = CGetSetOptions::GetPath(PATH_EDIT_CLIPS);
- m_folderHandle = CreateFileW(editClipFolder, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
-
- m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
- AddEvent(EVENT_FILE_CHANGED, m_overlapped.hEvent);
- RefreshWatch();
- Start();
- }
- void CClipEditThread::WatchFile(CString filePath)
- {
- ATL::CCritSecLock csLock(m_fileEditsLock.m_sect);
- nsPath::CPath path(filePath);
- //start the edit count at 0, the first edit notification is us saving the file, after that handle the file change
- m_fileEditStarts[path.GetName()] = CTime::GetCurrentTime();
- }
- void CClipEditThread::RefreshWatch()
- {
- memset(m_fileChangeBuffer, 0, sizeof(m_fileChangeBuffer));
- DWORD bytesReturned = 0;
- ReadDirectoryChangesW(m_folderHandle, m_fileChangeBuffer, sizeof(m_fileChangeBuffer), FALSE,
- FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE,
- &bytesReturned, &m_overlapped, NULL);
- }
- void CClipEditThread::OnTimeOut(void* param)
- {
- if (m_waitTimeout == MAX_TIMEOUT)
- {
- CString editClipFolder = CGetSetOptions::GetPath(PATH_EDIT_CLIPS);
- DeleteFolderFiles(editClipFolder, TRUE, CTimeSpan(7, 0, 0, 0));
- //cleanup up the list of edits that we started, after 7 days just keeps the list from growing too large, hopefull they don't have millions of edits in 7 days
- for (auto it = m_fileEditStarts.begin(); it != m_fileEditStarts.end();)
- {
- auto diff = CTime::GetCurrentTime() - it->second;
- if (diff.GetTotalSeconds() > (86400 * 7))
- {
- it = m_fileEditStarts.erase(it);
- }
- else
- {
- it++;
- }
- }
- }
- else
- {
- for (auto const& toSave : m_filesToSave)
- {
- CString fileName = toSave.first;
- CString editClipFileName = _T("EditClip_");
- CString newClipFileName = _T("NewClip_");
- if (fileName.Find(editClipFileName, 0) == 0)
- {
- nsPath::CPath path(fileName);
- CString idString = path.GetTitle().Mid(editClipFileName.GetLength());
- int id = _wtoi(idString);
- if (id > 0)
- {
- SaveToClip(fileName, id);
- }
- }
- else if (fileName.Find(newClipFileName, 0) == 0)
- {
- SaveToClip(fileName, -1);
- }
- }
- m_filesToSave.clear();
- m_waitTimeout = MAX_TIMEOUT;
- }
- }
- void CClipEditThread::OnEvent(int eventId, void* param)
- {
- switch (eventId)
- {
- case EVENT_FILE_CHANGED:
- {
- OnFileChanged();
- break;
- }
- }
- }
- void CClipEditThread::OnFileChanged()
- {
- FILE_NOTIFY_INFORMATION* pNotify = m_fileChangeBuffer;
- int loopCount = 0;
- bool fileModified = false;
- CString editClipFileName = _T("EditClip_");
- CString newClipFileName = _T("NewClip_");
- while (true)
- {
- CString fileName(pNotify->FileName, pNotify->FileNameLength / sizeof(WCHAR));
-
- //we can't filter by the action, modify as ms word doesn't modify the file they replace it
- ///so just look for anything that matches our file names
- if (pNotify->Action != FILE_ACTION_ADDED && pNotify->Action != FILE_ACTION_REMOVED)
- {
- bool addToChanges = true;
- {
- ATL::CCritSecLock csLock(m_fileEditsLock.m_sect);
- auto exists = m_fileEditStarts.find(fileName);
- if (exists != m_fileEditStarts.end())
- {
- auto startEdit = m_fileEditStarts[fileName];
- auto diff = CTime::GetCurrentTime() - startEdit;
- if (diff.GetTotalSeconds() < CGetSetOptions::m_clipEditSaveDelayAfterLoadSeconds)
- {
- Log(StrF(_T("%s has changed close to when we started editing the file, diff: %lld, limit: %d, not handling change"), fileName, diff.GetTotalSeconds(), CGetSetOptions::m_clipEditSaveDelayAfterLoadSeconds));
- addToChanges = false;
- }
- }
- else //not in our list of files we initiated the change with
- {
- if (fileName.Find(newClipFileName, 0) == 0)
- {
- Log(StrF(_T("New clip file changed: %s, this was not in Ditto list of files we initiated the change for, not handling change"), fileName));
- addToChanges = false;
- }
- }
- }
- if (fileName.Find(editClipFileName, 0) == -1 && fileName.Find(newClipFileName, 0) == -1)
- {
- addToChanges = false;
- Log(StrF(_T("File %s is not a Ditto file of format EditClip or NewClip, not handling change"), fileName));
- }
- if (addToChanges)
- {
- Log(StrF(_T("%s file changed, adding to list to be saved back to Ditto"), fileName));
- m_filesToSave[fileName] = true;
- fileModified = true;
- }
- }
- if (pNotify->NextEntryOffset <= 0 || loopCount > 1000)
- {
- break;
- }
- pNotify = (FILE_NOTIFY_INFORMATION*)((BYTE*)pNotify + pNotify->NextEntryOffset);
- if (pNotify == nullptr)
- {
- break;
- }
- loopCount++;
- }
- RefreshWatch();
- if (fileModified)
- {
- m_waitTimeout = CGetSetOptions::m_clipEditSaveDelayAfterSaveSeconds * 1000;
- }
- }
- bool CClipEditThread::SaveToClip(CString filePath, int id)
- {
- bool savedClip = false;
- Log(StrF(_T("ClipFile: %s, ClipId: %d, has changed saving back to Ditto"), filePath, id));
- if (id < 0)
- {
- auto exists = m_newClipIds.find(filePath);
- if (exists != m_newClipIds.end())
- {
- id = m_newClipIds[filePath];
- }
- }
- CClip clip;
- if (id >= 0)
- {
- if (clip.LoadMainTable(id) == FALSE)
- {
- Log(StrF(_T("Error loading clip id: %d, not saving"), id));
- return false;
- }
- clip.LoadFormats(id);
- }
- CString unicodeText;
- CStringA utf8Text;
- bool unicode = false;
- std::vector<BYTE> cf_dibBytes;
- std::vector<BYTE> pngBytes;
-
- CString editClipFolder = CGetSetOptions::GetPath(PATH_EDIT_CLIPS);
- CString fullFilePath = editClipFolder + filePath;
- nsPath::CPath path(filePath);
- auto extenstion = path.GetExtension().MakeLower();
- if (extenstion == _T("png") || extenstion == _T("bmp"))
- {
- if (ReadImageFile(fullFilePath, cf_dibBytes, pngBytes) == false)
- {
- Log(StrF(_T("Error reading image file %s, clip id: %d, not saving"), fullFilePath, id));
- return false;
- }
- }
- else if (ReadFile(fullFilePath, unicode, unicodeText, utf8Text) == false)
- {
- Log(StrF(_T("Error reading text file %s, clip id: %d, not saving"), fullFilePath, id));
- return false;
- }
- if (id < 0 &&
- unicodeText == _T("") &&
- utf8Text == "" &&
- cf_dibBytes.size() <= 0 &&
- pngBytes.size() <= 0)
- {
- Log(StrF(_T("Not saving new clip that is empty, no text or image bytes, path: %s, clip id: %d, not saving"), fullFilePath, id));
- return false;
- }
- BOOL modifyDescription = CGetSetOptions::GetUpdateDescWhenSavingClip();
-
- if (extenstion == _T("bmp") || extenstion == _T("png"))
- {
- clip.SaveFormats(nullptr, nullptr, nullptr, modifyDescription, &cf_dibBytes, &pngBytes);
- }
- else if (extenstion == _T("txt"))
- {
- if (unicode)
- {
- clip.SaveFormats(&unicodeText, nullptr, nullptr, modifyDescription);
- }
- else
- {
- unicodeText = CTextConvert::Utf8ToUnicode(utf8Text);
- clip.SaveFormats(&unicodeText, nullptr, nullptr, modifyDescription);
- }
- }
- else if (extenstion == _T("rtf"))
- {
- if (GetTextFromRTF(utf8Text, unicodeText))
- {
- clip.SaveFormats(&unicodeText, nullptr, &utf8Text, modifyDescription);
- }
- else
- {
- clip.SaveFormats(nullptr, nullptr, &utf8Text, modifyDescription);
- }
- }
- //refresh the clip in the UI
- if (id == -1)
- {
- m_newClipIds[filePath] = clip.m_id;
- theApp.RefreshView(CopyReasonEnum::COPY_TO_UNKOWN);
- }
- else if (id > 0)
- {
- theApp.RefreshClipInUI(id, UPDATE_CLIP_DESCRIPTION);
- }
- savedClip = true;
- return savedClip;
- }
- bool CClipEditThread::ReadFile(CString filePath, bool &unicode, CString &unicodeText, CStringA &utf8Text)
- {
- CFile file;
- CFileException ex;
- if (!file.Open(filePath, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone, &ex))
- {
- CString error;
- ex.GetErrorMessage(error.GetBufferSetLength(200), 200);
- error.ReleaseBuffer();
- log(StrF(_T("LoadFormatsFromFile - Error opening file: %s, Error: %s\r\n"), filePath, error));
- return false;
- }
- if (file.GetLength() >= 2)
- {
- wchar_t header;
- file.Read(&header, sizeof(wchar_t));
- if (header == 0xFEFF)
- {
- unicode = true;
- }
- else
- {
- file.SeekToBegin();
- }
- }
- if (unicode)
- {
- const UINT bufferSize = (UINT)((file.GetLength() - 2) / 2);
- file.Read(unicodeText.GetBufferSetLength(bufferSize), bufferSize * 2);
- unicodeText.ReleaseBuffer();
- }
- else
- {
- const UINT bufferSize = (UINT)(file.GetLength());
- file.Read(utf8Text.GetBufferSetLength(bufferSize), bufferSize);
- utf8Text.ReleaseBuffer();
- }
- return true;
- }
- std::vector<BYTE> CImageToPNGBytes(const CImage& image, REFGUID guidFileType)
- {
- IStream* pStream = nullptr;
- HRESULT hr = CreateStreamOnHGlobal(nullptr, TRUE, &pStream);
- if (FAILED(hr)) {
- return {};
- }
- ULARGE_INTEGER ulSize;
- hr = image.Save(pStream, guidFileType);
- if (FAILED(hr)) {
- pStream->Release();
- return {};
- }
- LARGE_INTEGER liZero = { 0 };
- hr = pStream->Seek(liZero, STREAM_SEEK_SET, nullptr);
- if (FAILED(hr)) {
- pStream->Release();
- return {};
- }
- hr = pStream->Seek({ 0 }, STREAM_SEEK_END, &ulSize);
- if (FAILED(hr)) {
- pStream->Release();
- return {};
- }
- std::vector<BYTE> pngBytes((UINT)ulSize.QuadPart);
- hr = pStream->Seek(liZero, STREAM_SEEK_SET, nullptr);
- if (FAILED(hr)) {
- pStream->Release();
- return {};
- }
- hr = pStream->Read(pngBytes.data(), (UINT)ulSize.QuadPart, nullptr);
- pStream->Release();
- if (FAILED(hr)) {
- return {};
- }
- return pngBytes;
- }
- bool CClipEditThread::ReadImageFile(CString path, std::vector<BYTE> &cf_dibBytes, std::vector<BYTE> & pngBytes)
- {
- CImage image;
- HRESULT hr = image.Load(path);
- if (FAILED(hr))
- {
- Log(StrF(_T("Failed to load image, %s"), path));
- return false;
- }
- pngBytes = CImageToPNGBytes(image, Gdiplus::ImageFormatPNG);
- return true;
- }
- BOOL CClipEditThread::GetTextFromRTF(CStringA rtf, CString &unicodeText)
- {
- CConvertRTFToText cc;
- if (cc.Create())
- {
- unicodeText = cc.GetTextFromRTF(rtf);
- cc.DestroyWindow();
- if (rtf != "" && unicodeText == "")
- {
- log(StrF(_T("Failed to convert rtf to text, rtf text is not empty but text is empty")));
- }
- return true;
- }
- else
- {
- log(StrF(_T("Failed to create rtf to text window")));
- }
- return false;
- }
|