DatabaseUtilities.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. // DatabaseUtilites.cpp: implementation of the CDatabaseUtilites class.
  2. //
  3. //////////////////////////////////////////////////////////////////////
  4. #include "stdafx.h"
  5. #include "CP_Main.h"
  6. #include "DatabaseUtilities.h"
  7. #include "ProcessPaste.h"
  8. #include <io.h>
  9. //////////////////////////////////////////////////////////////////////
  10. // Construction/Destruction
  11. //////////////////////////////////////////////////////////////////////
  12. BOOL CreateBackup(CString csPath)
  13. {
  14. CString csOriginal;
  15. int count = 0;
  16. // create a backup of the existing database
  17. do
  18. {
  19. count++;
  20. csOriginal = csPath + StrF(".%03d",count);
  21. // in case of some weird infinite loop
  22. if( count > 50 )
  23. {
  24. ASSERT(0);
  25. return FALSE;
  26. }
  27. } while( !::CopyFile(csPath, csOriginal, TRUE) );
  28. return TRUE;
  29. }
  30. CString GetDBName()
  31. {
  32. return CGetSetOptions::GetDBPath();
  33. }
  34. CString GetDefaultDBName()
  35. {
  36. CString csDefaultPath;
  37. LPMALLOC pMalloc;
  38. if(SUCCEEDED(::SHGetMalloc(&pMalloc)))
  39. {
  40. LPITEMIDLIST pidlPrograms;
  41. SHGetSpecialFolderLocation(NULL, CSIDL_APPDATA, &pidlPrograms);
  42. char string[MAX_PATH];
  43. SHGetPathFromIDList(pidlPrograms, string);
  44. pMalloc->Free(pidlPrograms);
  45. pMalloc->Release();
  46. csDefaultPath = string;
  47. csDefaultPath += "\\Ditto\\";
  48. if(_access(csDefaultPath, 0) == -1)
  49. CreateDirectory(csDefaultPath, NULL);
  50. csDefaultPath += DEFAULT_DB_NAME;
  51. }
  52. return csDefaultPath;
  53. }
  54. BOOL CheckDBExists(CString csDBPath)
  55. {
  56. if(_access(csDBPath, 0) == -1)
  57. {
  58. //Database didn't exist
  59. CGetSetOptions::SetDBPath("");
  60. // -- create a new one
  61. return CreateDB(GetDefaultDBName());
  62. }
  63. BOOL bRet = FALSE;
  64. if(ValidDB(csDBPath) == FALSE)
  65. {
  66. theApp.CloseDB();
  67. CGetSetOptions::SetDBPath("");
  68. //Db existed but was bad
  69. CString csMarkAsBad;
  70. csMarkAsBad = csDBPath;
  71. csMarkAsBad.Replace(".", "_BAD.");
  72. CString csPath = GetDefaultDBName();
  73. CString cs;
  74. cs.Format("Unrecognized Database Format \"%s\",\n"
  75. "the file will be renamed \"%s\",\n"
  76. "and a new database will be created,\n"
  77. "\"%s\"", csDBPath, csMarkAsBad, csPath);
  78. AfxMessageBox(cs);
  79. CFile::Rename(csDBPath, csMarkAsBad);
  80. bRet = CreateDB(csPath);
  81. }
  82. else
  83. bRet = TRUE;
  84. return bRet;
  85. }
  86. // m_pErrorInfo:
  87. // - m_lErrorCode 0x00000cc1
  88. // - m_strSource "DAO.Fields"
  89. // - m_strDescription "Item not found in this collection."
  90. #define ON_FIELD_ABSENT(name,onabsent) \
  91. try { table.GetFieldInfo(name,info); } \
  92. catch(CDaoException* e) \
  93. { \
  94. if( !bUpgrade || e->m_pErrorInfo->m_lErrorCode != 0x00000cc1 ) \
  95. throw e; \
  96. if( bUpgraded == FALSE ) \
  97. CreateBackup(csPath); \
  98. bResult &= onabsent; \
  99. bUpgraded = TRUE; \
  100. e->Delete(); \
  101. }
  102. BOOL ValidDB(CString csPath, BOOL bUpgrade)
  103. {
  104. BOOL bResult = TRUE;
  105. BOOL bUpgraded = FALSE;
  106. try
  107. {
  108. CDaoDatabase db;
  109. db.Open(csPath);
  110. CDaoTableDef table(&db);
  111. CDaoFieldInfo info;
  112. table.Open("Main");
  113. table.GetFieldInfo("lID", info);
  114. table.GetFieldInfo("lDate", info);
  115. ON_FIELD_ABSENT("mText", Upgrade_mText(db)); // +mText, -strText, -strType
  116. table.GetFieldInfo("lShortCut", info);
  117. table.GetFieldInfo("lDontAutoDelete", info);
  118. table.GetFieldInfo("lTotalCopySize", info);
  119. ON_FIELD_ABSENT("bIsGroup", Upgrade_Groups(db));
  120. table.GetFieldInfo("lParentID", info); // part of Upgrade_Groups
  121. table.GetFieldInfo("dOrder", info); // part of Upgrade_Groups
  122. ON_FIELD_ABSENT("lDataID", Upgrade_ShareData(db)); // +lDataID, -lParentID
  123. table.Close();
  124. table.Open("Data");
  125. table.GetFieldInfo("lID", info);
  126. table.GetFieldInfo("lDataID", info); // part of Upgrade_ShareData()
  127. table.GetFieldInfo("strClipBoardFormat", info);
  128. table.GetFieldInfo("ooData", info);
  129. table.Close();
  130. table.Open("Types");
  131. table.GetFieldInfo("ID", info);
  132. table.GetFieldInfo("TypeText", info);
  133. table.Close();
  134. }
  135. catch(CDaoException* e)
  136. {
  137. e->ReportError();
  138. ASSERT(FALSE);
  139. e->Delete();
  140. return FALSE;
  141. }
  142. // if we upgraded, perform full validation again without upgrading
  143. if( bUpgraded )
  144. return ValidDB( csPath, FALSE);
  145. return bResult;
  146. }
  147. BOOL CreateDB(CString csPath)
  148. {
  149. try
  150. {
  151. CDaoDatabase db;
  152. EnsureDirectory(csPath);
  153. db.Create(csPath);
  154. CDaoTableDefEx table(&db);
  155. //Create the Main table
  156. table.Create("Main");
  157. table.CreateField("lID", dbLong, 4, dbAutoIncrField);
  158. table.CreateIndex(TRUE, "lID");
  159. table.CreateField("lDate", dbLong, 4, 0, "0");
  160. table.CreateIndex(FALSE, "lDate");
  161. table.CreateField("mText", dbMemo, 0, dbVariableField);
  162. table.CreateField("lShortCut", dbLong, 4, 0, "0");
  163. table.CreateIndex(FALSE, "lShortCut");
  164. table.CreateField("lDontAutoDelete", dbLong, 4, 0, "0");
  165. table.CreateField("lTotalCopySize", dbLong, 4, 0, "0");
  166. // GROUPS
  167. table.CreateField("bIsGroup", dbBoolean, 1, 0, "0"); // for Groups
  168. table.CreateIndex(FALSE, "bIsGroup");
  169. table.CreateField("lParentID", dbLong, 4, 0, "0"); // parent Group Main.lID
  170. table.CreateIndex(FALSE, "lParentID");
  171. table.CreateField("dOrder", dbDouble, 8, 0, "0"); // for Order within Groups
  172. table.CreateIndex(FALSE, "dOrder");
  173. // for sharing data amongst multiple clips
  174. table.CreateField("lDataID", dbLong, 4, 0, "0"); // corresponds to Data.lDataID
  175. table.CreateIndex(FALSE, "lDataID");
  176. table.Append();
  177. table.Close();
  178. //Create the Data Table
  179. table.Create("Data");
  180. table.CreateField("lID", dbLong, 4, dbAutoIncrField);
  181. table.CreateIndex(TRUE, "lID");
  182. table.CreateField("lDataID", dbLong, 4, 0, "0");
  183. table.CreateIndex(FALSE, "lDataID");
  184. table.CreateField("strClipBoardFormat", dbText, 50, dbVariableField);
  185. table.CreateField("ooData", dbLongBinary, 0);
  186. table.Append();
  187. table.Close();
  188. //Create the Types table
  189. table.Create("Types");
  190. table.CreateField("ID", dbLong, 4, dbAutoIncrField);
  191. table.CreateField("TypeText", dbText, 50, dbVariableField);
  192. table.Append();
  193. table.Close();
  194. db.Close();
  195. return TRUE;
  196. }
  197. catch(CDaoException *e)
  198. {
  199. e->ReportError();
  200. ASSERT(FALSE);
  201. e->Delete();
  202. }
  203. return FALSE;
  204. }
  205. // +mText, -strText, -strType
  206. BOOL Upgrade_mText(CDaoDatabase& db)
  207. {
  208. try
  209. {
  210. db.Execute("ALTER TABLE Main ADD COLUMN mText MEMO", dbFailOnError);
  211. db.Execute("UPDATE Main SET mText=strText", dbFailOnError);
  212. db.Execute("ALTER TABLE Main DROP COLUMN strText", dbFailOnError);
  213. db.Execute("ALTER TABLE Main DROP COLUMN strType", dbFailOnError);
  214. }
  215. CATCHDAO
  216. return TRUE;
  217. }
  218. BOOL Upgrade_Groups(CDaoDatabase& db)
  219. {
  220. try
  221. {
  222. CDaoTableDefEx table(&db);
  223. table.Open("Main");
  224. // Groups
  225. table.CreateField("bIsGroup", dbBoolean, 1, 0, "0"); // for Groups
  226. table.CreateIndex(FALSE, "bIsGroup");
  227. table.CreateField("lParentID", dbLong, 4, 0, "0"); // parent Group Main.lID
  228. table.CreateIndex(FALSE, "lParentID");
  229. table.CreateField("dOrder", dbDouble, 8, 0, "0"); // for Order within Groups
  230. table.CreateIndex(FALSE, "dOrder");
  231. table.Close();
  232. // set defaults (otherwise might be NULL)
  233. db.Execute("UPDATE Main SET bIsGroup = 0, lParentID = 0, dOrder = 0", dbFailOnError);
  234. }
  235. CATCHDAO
  236. return TRUE;
  237. }
  238. BOOL Upgrade_ShareData(CDaoDatabase& db)
  239. {
  240. CPopup status(10000,10000); // peg at the bottom-right corner of screen
  241. try
  242. {
  243. CDaoTableDefEx table(&db);
  244. table.Open("Main");
  245. table.CreateField("lDataID", dbLong, 4, 0, "0"); // corresponds to Data.lDataID
  246. table.CreateIndex(FALSE, "lDataID");
  247. table.Close();
  248. table.Open("Data");
  249. table.CreateField("lDataID", dbLong, 4, 0, "0"); // parent Group Main.lID
  250. table.CreateIndex(FALSE, "lDataID");
  251. table.Close();
  252. // set defaults
  253. db.Execute( "UPDATE Main SET lDataID = 0", dbFailOnError );
  254. db.Execute( "UPDATE Data SET lDataID = 0", dbFailOnError );
  255. // update Main.lDataID and Data.lParentID for sharing Data
  256. //
  257. // - multiple Formats (Data.lID) exist for a single ClipData (Data.lDataID)
  258. // - The value of lDataID is arbitrary, but must be unique to the ClipData.
  259. // - In order to ensure uniqueness, lDataID is assigned the lID of
  260. // the first Format in the Clip's set.
  261. COleVariant var((long)0);
  262. CDaoRecordset main(&db);
  263. long main_fldID;
  264. long main_fldDataID;
  265. long main_lID;
  266. CDaoRecordset data(&db);
  267. long data_fldID;
  268. long data_fldDataID;
  269. long lDataID;
  270. int count = 0;
  271. int i = 0;
  272. int percentPrev = -1;
  273. int percent = -1;
  274. main.Open(dbOpenDynaset,"SELECT lID, lDataID FROM Main");
  275. main_fldID = GetFieldPos(main,"lID");
  276. VERIFY(main_fldID == 0);
  277. main_fldDataID = GetFieldPos(main,"lDataID");
  278. VERIFY(main_fldDataID == 1);
  279. if( !main.IsEOF() )
  280. {
  281. main.MoveLast();
  282. count = main.GetRecordCount();
  283. main.MoveFirst();
  284. }
  285. // for each record in Main and its corresponding records in Data,
  286. // assign a new unique lDataID.
  287. while( !main.IsEOF() )
  288. {
  289. i++;
  290. percentPrev = percent;
  291. percent = (i*100)/count;
  292. if( percent != percentPrev )
  293. status.Show(StrF("Ditto: Upgrading database (%d%%)",percent));
  294. main.GetFieldValue(main_fldID,var);
  295. main_lID = var.lVal;
  296. data.Open(dbOpenDynaset, StrF(
  297. "SELECT lID, lDataID "
  298. "FROM Data WHERE lParentID = %d", main_lID) );
  299. data_fldID = GetFieldPos(data,"lID");
  300. VERIFY(data_fldID == 0);
  301. data_fldDataID = GetFieldPos(data,"lDataID");
  302. VERIFY(data_fldDataID == 1);
  303. // lDataID = the first data record lID
  304. lDataID = 0;
  305. if( !data.IsEOF() )
  306. {
  307. data.GetFieldValue(0,var); // 0 == lID field
  308. lDataID = var.lVal;
  309. }
  310. // assign all Data records the same lDataID
  311. while( !data.IsEOF() )
  312. {
  313. var.lVal = lDataID;
  314. data.Edit();
  315. data.SetFieldValue(1,var); // 1 == lDataID field
  316. data.Update();
  317. data.MoveNext();
  318. }
  319. // assign Main.lDataID
  320. var.lVal = lDataID;
  321. main.Edit();
  322. main.SetFieldValue(1,var); // 1 == lDataID field
  323. main.Update();
  324. main.MoveNext();
  325. data.Close();
  326. }
  327. main.Close();
  328. // delete the old field
  329. db.Execute("ALTER TABLE Data DROP CONSTRAINT lParentID", dbFailOnError);
  330. db.Execute("ALTER TABLE Data DROP COLUMN lParentID", dbFailOnError);
  331. }
  332. CATCHDAO
  333. return TRUE;
  334. }
  335. BOOL CompactDatabase()
  336. {
  337. if(!theApp.CloseDB())
  338. return FALSE;
  339. CString csDBName = GetDBName();
  340. CString csTempDBName = csDBName;
  341. csTempDBName.Replace(".mdb", "TempDBName.mdb");
  342. //Compact the database
  343. try
  344. {
  345. CDaoWorkspace::CompactDatabase(csDBName, csTempDBName);//, dbLangGeneral, 0, "andrew");//DATABASE_PASSWORD);
  346. }
  347. catch(CDaoException* e)
  348. {
  349. AfxMessageBox(e->m_pErrorInfo->m_strDescription);
  350. DeleteFile(csTempDBName);
  351. e->Delete();
  352. return FALSE;
  353. }
  354. catch(CMemoryException* e)
  355. {
  356. AfxMessageBox("Memory Exception");
  357. DeleteFile(csTempDBName);
  358. e->Delete();
  359. return FALSE;
  360. }
  361. //Since compacting the database creates a new db delete the old one and replace it
  362. //with the compacted db
  363. if(DeleteFile(csDBName))
  364. {
  365. try
  366. {
  367. CFile::Rename(csTempDBName, csDBName);
  368. }
  369. catch(CFileException *e)
  370. {
  371. e->ReportError();
  372. e->Delete();
  373. return FALSE;
  374. }
  375. }
  376. else
  377. AfxMessageBox("Error Compacting Database");
  378. return TRUE;
  379. }
  380. BOOL RepairDatabase()
  381. {
  382. if(!theApp.CloseDB())
  383. return FALSE;
  384. try
  385. {
  386. CDaoWorkspace::RepairDatabase(GetDBName());
  387. }
  388. catch(CDaoException *e)
  389. {
  390. AfxMessageBox(e->m_pErrorInfo->m_strDescription);
  391. e->Delete();
  392. return FALSE;
  393. }
  394. return TRUE;
  395. }
  396. BOOL RemoveOldEntries()
  397. {
  398. if(CGetSetOptions::GetCheckForMaxEntries())
  399. {
  400. long lMax = CGetSetOptions::GetMaxEntries();
  401. CMainTable recset;
  402. recset.Open("SELECT * FROM Main ORDER BY lDate DESC");
  403. if(!recset.IsEOF())
  404. {
  405. recset.MoveLast();
  406. long lCount = recset.GetRecordCount();
  407. CClipIDs IDs;
  408. while((lCount > lMax) && (!recset.IsBOF()))
  409. {
  410. //Only delete entries that have no shortcut and don't have the flag set
  411. if(recset.m_lShortCut == 0 && recset.m_lDontAutoDelete == 0)
  412. IDs.Add(recset.m_lID);
  413. lCount--;
  414. recset.MovePrev();
  415. }
  416. IDs.DeleteIDs();
  417. }
  418. }
  419. if(CGetSetOptions::GetCheckForExpiredEntries())
  420. {
  421. long lExpire = CGetSetOptions::GetExpiredEntries();
  422. if(lExpire)
  423. {
  424. CTime now = CTime::GetCurrentTime();
  425. now -= CTimeSpan(lExpire, 0, 0, 0);
  426. CMainTable recset;
  427. recset.Open("SELECT * FROM Main "
  428. "WHERE lDate < %d AND "
  429. "lShortCut = 0 AND lDontAutoDelete = 0", now.GetTime());
  430. CClipIDs IDs;
  431. while(!recset.IsEOF())
  432. {
  433. IDs.Add(recset.m_lID);
  434. recset.MoveNext();
  435. }
  436. IDs.DeleteIDs();
  437. }
  438. }
  439. return TRUE;
  440. }
  441. BOOL EnsureDirectory(CString csPath)
  442. {
  443. char drive[_MAX_DRIVE];
  444. char dir[_MAX_DIR];
  445. char fname[_MAX_FNAME];
  446. char ext[_MAX_EXT];
  447. _splitpath(csPath, drive, dir, fname, ext);
  448. CString csDir(drive);
  449. csDir += dir;
  450. if(_access(csDir, 0) == -1)
  451. {
  452. if(CreateDirectory(csDir, NULL))
  453. return TRUE;
  454. }
  455. else
  456. return TRUE;
  457. return FALSE;
  458. }
  459. BOOL ExecuteSQL( CString csSQL, BOOL bReportError, CDaoException** ppEx )
  460. {
  461. try
  462. {
  463. theApp.EnsureOpenDB();
  464. theApp.m_pDatabase->Execute(csSQL, dbFailOnError);
  465. }
  466. catch(CDaoException* e)
  467. {
  468. if( bReportError )
  469. e->ReportError();
  470. if( ppEx )
  471. *ppEx = e;
  472. else
  473. e->Delete();
  474. return FALSE;
  475. }
  476. return TRUE;
  477. }
  478. int GetFieldPos(CDaoRecordset& recs, LPCTSTR fieldName)
  479. {
  480. CDaoFieldInfo fi;
  481. int count = recs.GetFieldCount();
  482. for( int i = 0; i < count; i++ )
  483. {
  484. recs.GetFieldInfo(i, fi);
  485. if( fi.m_strName.Compare( fieldName ) == 0 )
  486. return i; // when found a match, return it
  487. }
  488. return -1;
  489. }
  490. void VerifyFieldPos(CDaoRecordset& recs, LPCTSTR fieldName, int index)
  491. {
  492. CDaoFieldInfo fi;
  493. int count = recs.GetFieldCount();
  494. VERIFY( index >= 0 && index < count );
  495. recs.GetFieldInfo(index, fi);
  496. VERIFY( fi.m_strName.Compare( fieldName ) == 0 );
  497. }
  498. CString GetFieldList(CDaoRecordset& recs)
  499. {
  500. CString field;
  501. CString list;
  502. CDaoFieldInfo fi;
  503. int count = recs.GetFieldCount();
  504. for( int i = 0; i < count; i++ )
  505. {
  506. recs.GetFieldInfo(i, fi);
  507. field = StrF("\n%d: ",i) + fi.m_strName;
  508. list += field;
  509. }
  510. return list;
  511. }