ClipEditThread.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453
  1. #include "stdafx.h"
  2. #include "ClipEditThread.h"
  3. #include "Options.h"
  4. #include "Misc.h"
  5. #include "Clip.h"
  6. #include "CP_Main.h"
  7. #include "ConvertRTFToText.h"
  8. #include "..\Shared\TextConvert.h"
  9. #define EVENT_FILE_CHANGED 1
  10. #define MAX_TIMEOUT 86400
  11. CClipEditThread::CClipEditThread()
  12. {
  13. m_folderHandle = INVALID_HANDLE_VALUE;
  14. m_threadName = _T("ClipEditTrackingThread");
  15. m_waitTimeout = MAX_TIMEOUT;
  16. }
  17. CClipEditThread::~CClipEditThread()
  18. {
  19. Close();
  20. }
  21. void CClipEditThread::Close()
  22. {
  23. Stop();
  24. if (m_folderHandle != INVALID_HANDLE_VALUE)
  25. {
  26. CloseHandle(m_folderHandle);
  27. m_folderHandle = INVALID_HANDLE_VALUE;
  28. }
  29. RemoveEvent(EVENT_FILE_CHANGED);
  30. m_overlapped.hEvent = INVALID_HANDLE_VALUE;
  31. CString editClipFolder = CGetSetOptions::GetPath(PATH_EDIT_CLIPS);
  32. DeleteFolderFiles(editClipFolder, TRUE, CTimeSpan(7, 0, 0, 0));
  33. }
  34. void CClipEditThread::StartWatchingFolderForChanges()
  35. {
  36. CString editClipFolder = CGetSetOptions::GetPath(PATH_EDIT_CLIPS);
  37. 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);
  38. m_overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  39. AddEvent(EVENT_FILE_CHANGED, m_overlapped.hEvent);
  40. RefreshWatch();
  41. Start();
  42. }
  43. void CClipEditThread::WatchFile(CString filePath)
  44. {
  45. ATL::CCritSecLock csLock(m_fileEditsLock.m_sect);
  46. nsPath::CPath path(filePath);
  47. //start the edit count at 0, the first edit notification is us saving the file, after that handle the file change
  48. m_fileEditStarts[path.GetName()] = CTime::GetCurrentTime();
  49. }
  50. void CClipEditThread::RefreshWatch()
  51. {
  52. memset(m_fileChangeBuffer, 0, sizeof(m_fileChangeBuffer));
  53. DWORD bytesReturned = 0;
  54. ReadDirectoryChangesW(m_folderHandle, m_fileChangeBuffer, sizeof(m_fileChangeBuffer), FALSE,
  55. FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_SIZE,
  56. &bytesReturned, &m_overlapped, NULL);
  57. }
  58. void CClipEditThread::OnTimeOut(void* param)
  59. {
  60. if (m_waitTimeout == MAX_TIMEOUT)
  61. {
  62. CString editClipFolder = CGetSetOptions::GetPath(PATH_EDIT_CLIPS);
  63. DeleteFolderFiles(editClipFolder, TRUE, CTimeSpan(7, 0, 0, 0));
  64. //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
  65. for (auto it = m_fileEditStarts.begin(); it != m_fileEditStarts.end();)
  66. {
  67. auto diff = CTime::GetCurrentTime() - it->second;
  68. if (diff.GetTotalSeconds() > (86400 * 7))
  69. {
  70. it = m_fileEditStarts.erase(it);
  71. }
  72. else
  73. {
  74. it++;
  75. }
  76. }
  77. }
  78. else
  79. {
  80. for (auto const& toSave : m_filesToSave)
  81. {
  82. CString fileName = toSave.first;
  83. CString editClipFileName = _T("EditClip_");
  84. CString newClipFileName = _T("NewClip_");
  85. if (fileName.Find(editClipFileName, 0) == 0)
  86. {
  87. nsPath::CPath path(fileName);
  88. CString idString = path.GetTitle().Mid(editClipFileName.GetLength());
  89. int id = _wtoi(idString);
  90. if (id > 0)
  91. {
  92. SaveToClip(fileName, id);
  93. }
  94. }
  95. else if (fileName.Find(newClipFileName, 0) == 0)
  96. {
  97. SaveToClip(fileName, -1);
  98. }
  99. }
  100. m_filesToSave.clear();
  101. m_waitTimeout = MAX_TIMEOUT;
  102. }
  103. }
  104. void CClipEditThread::OnEvent(int eventId, void* param)
  105. {
  106. switch (eventId)
  107. {
  108. case EVENT_FILE_CHANGED:
  109. {
  110. OnFileChanged();
  111. break;
  112. }
  113. }
  114. }
  115. void CClipEditThread::OnFileChanged()
  116. {
  117. FILE_NOTIFY_INFORMATION* pNotify = m_fileChangeBuffer;
  118. int loopCount = 0;
  119. bool fileModified = false;
  120. CString editClipFileName = _T("EditClip_");
  121. CString newClipFileName = _T("NewClip_");
  122. while (true)
  123. {
  124. CString fileName(pNotify->FileName, pNotify->FileNameLength / sizeof(WCHAR));
  125. //we can't filter by the action, modify as ms word doesn't modify the file they replace it
  126. ///so just look for anything that matches our file names
  127. if (pNotify->Action != FILE_ACTION_ADDED && pNotify->Action != FILE_ACTION_REMOVED)
  128. {
  129. bool addToChanges = true;
  130. {
  131. ATL::CCritSecLock csLock(m_fileEditsLock.m_sect);
  132. auto exists = m_fileEditStarts.find(fileName);
  133. if (exists != m_fileEditStarts.end())
  134. {
  135. auto startEdit = m_fileEditStarts[fileName];
  136. auto diff = CTime::GetCurrentTime() - startEdit;
  137. if (diff.GetTotalSeconds() < CGetSetOptions::m_clipEditSaveDelayAfterLoadSeconds)
  138. {
  139. 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));
  140. addToChanges = false;
  141. }
  142. }
  143. else //not in our list of files we initiated the change with
  144. {
  145. if (fileName.Find(newClipFileName, 0) == 0)
  146. {
  147. 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));
  148. addToChanges = false;
  149. }
  150. }
  151. }
  152. if (fileName.Find(editClipFileName, 0) == -1 && fileName.Find(newClipFileName, 0) == -1)
  153. {
  154. addToChanges = false;
  155. Log(StrF(_T("File %s is not a Ditto file of format EditClip or NewClip, not handling change"), fileName));
  156. }
  157. if (addToChanges)
  158. {
  159. Log(StrF(_T("%s file changed, adding to list to be saved back to Ditto"), fileName));
  160. m_filesToSave[fileName] = true;
  161. fileModified = true;
  162. }
  163. }
  164. if (pNotify->NextEntryOffset <= 0 || loopCount > 1000)
  165. {
  166. break;
  167. }
  168. pNotify = (FILE_NOTIFY_INFORMATION*)((BYTE*)pNotify + pNotify->NextEntryOffset);
  169. if (pNotify == nullptr)
  170. {
  171. break;
  172. }
  173. loopCount++;
  174. }
  175. RefreshWatch();
  176. if (fileModified)
  177. {
  178. m_waitTimeout = CGetSetOptions::m_clipEditSaveDelayAfterSaveSeconds * 1000;
  179. }
  180. }
  181. bool CClipEditThread::SaveToClip(CString filePath, int id)
  182. {
  183. bool savedClip = false;
  184. Log(StrF(_T("ClipFile: %s, ClipId: %d, has changed saving back to Ditto"), filePath, id));
  185. if (id < 0)
  186. {
  187. auto exists = m_newClipIds.find(filePath);
  188. if (exists != m_newClipIds.end())
  189. {
  190. id = m_newClipIds[filePath];
  191. }
  192. }
  193. CClip clip;
  194. if (id >= 0)
  195. {
  196. if (clip.LoadMainTable(id) == FALSE)
  197. {
  198. Log(StrF(_T("Error loading clip id: %d, not saving"), id));
  199. return false;
  200. }
  201. clip.LoadFormats(id);
  202. }
  203. CString unicodeText;
  204. CStringA utf8Text;
  205. bool unicode = false;
  206. std::vector<BYTE> cf_dibBytes;
  207. std::vector<BYTE> pngBytes;
  208. CString editClipFolder = CGetSetOptions::GetPath(PATH_EDIT_CLIPS);
  209. CString fullFilePath = editClipFolder + filePath;
  210. nsPath::CPath path(filePath);
  211. auto extenstion = path.GetExtension().MakeLower();
  212. if (extenstion == _T("png") || extenstion == _T("bmp"))
  213. {
  214. if (ReadImageFile(fullFilePath, cf_dibBytes, pngBytes) == false)
  215. {
  216. Log(StrF(_T("Error reading image file %s, clip id: %d, not saving"), fullFilePath, id));
  217. return false;
  218. }
  219. }
  220. else if (ReadFile(fullFilePath, unicode, unicodeText, utf8Text) == false)
  221. {
  222. Log(StrF(_T("Error reading text file %s, clip id: %d, not saving"), fullFilePath, id));
  223. return false;
  224. }
  225. if (id < 0 &&
  226. unicodeText == _T("") &&
  227. utf8Text == "" &&
  228. cf_dibBytes.size() <= 0 &&
  229. pngBytes.size() <= 0)
  230. {
  231. Log(StrF(_T("Not saving new clip that is empty, no text or image bytes, path: %s, clip id: %d, not saving"), fullFilePath, id));
  232. return false;
  233. }
  234. BOOL modifyDescription = CGetSetOptions::GetUpdateDescWhenSavingClip();
  235. if (extenstion == _T("bmp") || extenstion == _T("png"))
  236. {
  237. clip.SaveFormats(nullptr, nullptr, nullptr, modifyDescription, &cf_dibBytes, &pngBytes);
  238. }
  239. else if (extenstion == _T("txt"))
  240. {
  241. if (unicode)
  242. {
  243. clip.SaveFormats(&unicodeText, nullptr, nullptr, modifyDescription);
  244. }
  245. else
  246. {
  247. unicodeText = CTextConvert::Utf8ToUnicode(utf8Text);
  248. clip.SaveFormats(&unicodeText, nullptr, nullptr, modifyDescription);
  249. }
  250. }
  251. else if (extenstion == _T("rtf"))
  252. {
  253. if (GetTextFromRTF(utf8Text, unicodeText))
  254. {
  255. clip.SaveFormats(&unicodeText, nullptr, &utf8Text, modifyDescription);
  256. }
  257. else
  258. {
  259. clip.SaveFormats(nullptr, nullptr, &utf8Text, modifyDescription);
  260. }
  261. }
  262. //refresh the clip in the UI
  263. if (id == -1)
  264. {
  265. m_newClipIds[filePath] = clip.m_id;
  266. theApp.RefreshView(CopyReasonEnum::COPY_TO_UNKOWN);
  267. }
  268. else if (id > 0)
  269. {
  270. theApp.RefreshClipInUI(id, UPDATE_CLIP_DESCRIPTION);
  271. }
  272. savedClip = true;
  273. return savedClip;
  274. }
  275. bool CClipEditThread::ReadFile(CString filePath, bool &unicode, CString &unicodeText, CStringA &utf8Text)
  276. {
  277. CFile file;
  278. CFileException ex;
  279. if (!file.Open(filePath, CFile::modeRead | CFile::typeBinary | CFile::shareDenyNone, &ex))
  280. {
  281. CString error;
  282. ex.GetErrorMessage(error.GetBufferSetLength(200), 200);
  283. error.ReleaseBuffer();
  284. log(StrF(_T("LoadFormatsFromFile - Error opening file: %s, Error: %s\r\n"), filePath, error));
  285. return false;
  286. }
  287. if (file.GetLength() >= 2)
  288. {
  289. wchar_t header;
  290. file.Read(&header, sizeof(wchar_t));
  291. if (header == 0xFEFF)
  292. {
  293. unicode = true;
  294. }
  295. else
  296. {
  297. file.SeekToBegin();
  298. }
  299. }
  300. if (unicode)
  301. {
  302. const UINT bufferSize = (UINT)((file.GetLength() - 2) / 2);
  303. file.Read(unicodeText.GetBufferSetLength(bufferSize), bufferSize * 2);
  304. unicodeText.ReleaseBuffer();
  305. }
  306. else
  307. {
  308. const UINT bufferSize = (UINT)(file.GetLength());
  309. file.Read(utf8Text.GetBufferSetLength(bufferSize), bufferSize);
  310. utf8Text.ReleaseBuffer();
  311. }
  312. return true;
  313. }
  314. std::vector<BYTE> CImageToPNGBytes(const CImage& image, REFGUID guidFileType)
  315. {
  316. IStream* pStream = nullptr;
  317. HRESULT hr = CreateStreamOnHGlobal(nullptr, TRUE, &pStream);
  318. if (FAILED(hr)) {
  319. return {};
  320. }
  321. ULARGE_INTEGER ulSize;
  322. hr = image.Save(pStream, guidFileType);
  323. if (FAILED(hr)) {
  324. pStream->Release();
  325. return {};
  326. }
  327. LARGE_INTEGER liZero = { 0 };
  328. hr = pStream->Seek(liZero, STREAM_SEEK_SET, nullptr);
  329. if (FAILED(hr)) {
  330. pStream->Release();
  331. return {};
  332. }
  333. hr = pStream->Seek({ 0 }, STREAM_SEEK_END, &ulSize);
  334. if (FAILED(hr)) {
  335. pStream->Release();
  336. return {};
  337. }
  338. std::vector<BYTE> pngBytes((UINT)ulSize.QuadPart);
  339. hr = pStream->Seek(liZero, STREAM_SEEK_SET, nullptr);
  340. if (FAILED(hr)) {
  341. pStream->Release();
  342. return {};
  343. }
  344. hr = pStream->Read(pngBytes.data(), (UINT)ulSize.QuadPart, nullptr);
  345. pStream->Release();
  346. if (FAILED(hr)) {
  347. return {};
  348. }
  349. return pngBytes;
  350. }
  351. bool CClipEditThread::ReadImageFile(CString path, std::vector<BYTE> &cf_dibBytes, std::vector<BYTE> & pngBytes)
  352. {
  353. CImage image;
  354. HRESULT hr = image.Load(path);
  355. if (FAILED(hr))
  356. {
  357. Log(StrF(_T("Failed to load image, %s"), path));
  358. return false;
  359. }
  360. pngBytes = CImageToPNGBytes(image, Gdiplus::ImageFormatPNG);
  361. return true;
  362. }
  363. BOOL CClipEditThread::GetTextFromRTF(CStringA rtf, CString &unicodeText)
  364. {
  365. CConvertRTFToText cc;
  366. if (cc.Create())
  367. {
  368. unicodeText = cc.GetTextFromRTF(rtf);
  369. cc.DestroyWindow();
  370. if (rtf != "" && unicodeText == "")
  371. {
  372. log(StrF(_T("Failed to convert rtf to text, rtf text is not empty but text is empty")));
  373. }
  374. return true;
  375. }
  376. else
  377. {
  378. log(StrF(_T("Failed to create rtf to text window")));
  379. }
  380. return false;
  381. }