Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

401 lines
11 KiB

  1. #include "shellprv.h"
  2. #include "util.h"
  3. #include "ids.h"
  4. #include "balmsg.h"
  5. #include "bitbuck.h"
  6. #include "mtpt.h"
  7. // states for state machine, values are relavant as we compare them
  8. // durring transitions to see if UI should be triggered or not
  9. typedef enum
  10. {
  11. STATE_1MB = 0, // the "disk is completely filled" case
  12. STATE_50MB = 1, // < 50MB case
  13. STATE_80MB = 2, // < 80MB case
  14. STATE_200MB = 3, // < 200MB case, only do this one on > 2.25GB drives
  15. STATE_ALLGOOD = 4, // > 200MB, everything is fine
  16. STATE_UNKNOWN = 5,
  17. } LOWDISK_STATE;
  18. #define BYTES_PER_MB ((ULONGLONG)0x100000)
  19. typedef struct
  20. {
  21. LOWDISK_STATE lds;
  22. ULONG dwMinMB; // range (min) that defines this state
  23. ULONG dwMaxMB; // range (max) that defines this state
  24. DWORD dwCleanupFlags; // DISKCLEANUP_
  25. DWORD dwShowTime; // in sec
  26. DWORD dwIntervalTime; // in sec
  27. UINT cRetry;
  28. DWORD niif;
  29. } STATE_DATA;
  30. #define HOURS (60 * 60)
  31. #define MINS (60)
  32. const STATE_DATA c_state_data[] =
  33. {
  34. {STATE_1MB, 0, 1, DISKCLEANUP_VERYLOWDISK, 30, 5 * MINS, -1, NIIF_ERROR},
  35. {STATE_50MB, 1, 50, DISKCLEANUP_VERYLOWDISK, 30, 5 * MINS, -1, NIIF_WARNING},
  36. {STATE_80MB, 50, 80, DISKCLEANUP_LOWDISK, 30, 4 * HOURS, 1, NIIF_WARNING},
  37. {STATE_200MB, 80, 200, DISKCLEANUP_LOWDISK, 30, 0 * HOURS, 0, NIIF_INFO},
  38. };
  39. void SRNotify(LPCWSTR pszDrive, DWORD dwFreeSpaceInMB, BOOL bImproving)
  40. {
  41. typedef void (* PFNSRNOTIFYFREESPACE)(LPCWSTR, DWORD, BOOL);
  42. static HMODULE s_hmodSR = NULL;
  43. if (NULL == s_hmodSR)
  44. s_hmodSR = LoadLibrary(TEXT("srclient.dll"));
  45. if (s_hmodSR)
  46. {
  47. PFNSRNOTIFYFREESPACE pfnNotifyFreeSpace = (PFNSRNOTIFYFREESPACE)GetProcAddress(s_hmodSR, "SRNotify");
  48. if (pfnNotifyFreeSpace)
  49. pfnNotifyFreeSpace(pszDrive, dwFreeSpaceInMB, bImproving);
  50. }
  51. }
  52. class CLowDiskSpaceUI : IQueryContinue
  53. {
  54. public:
  55. CLowDiskSpaceUI(int iDrive);
  56. void CheckDiskSpace();
  57. // IUnknown
  58. STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
  59. STDMETHODIMP_(ULONG) AddRef();
  60. STDMETHODIMP_(ULONG) Release();
  61. // IQueryContinue
  62. STDMETHODIMP QueryContinue(); // S_OK -> Continue, other
  63. private:
  64. ~CLowDiskSpaceUI();
  65. BOOL _EnterExclusive();
  66. void _LeaveExclusive();
  67. void _DoNotificationUI();
  68. void _DoStateMachine();
  69. const STATE_DATA *_StateData(LOWDISK_STATE lds);
  70. LOWDISK_STATE _StateFromFreeSpace(ULARGE_INTEGER ulTotal, ULARGE_INTEGER ulFree);
  71. LOWDISK_STATE _GetCurrentState(BOOL bInStateMachine);
  72. static DWORD CALLBACK s_ThreadProc(void *pv);
  73. LONG _cRef;
  74. TCHAR _szRoot[5];
  75. HANDLE _hMutex;
  76. LOWDISK_STATE _ldsCurrent;
  77. BOOL _bShowUI;
  78. BOOL _bSysVolume;
  79. DWORD _dwLastFreeMB;
  80. };
  81. CLowDiskSpaceUI::CLowDiskSpaceUI(int iDrive) : _cRef(1), _ldsCurrent(STATE_UNKNOWN)
  82. {
  83. ASSERT(_bShowUI == FALSE);
  84. ASSERT(_bSysVolume == FALSE);
  85. PathBuildRoot(_szRoot, iDrive);
  86. TCHAR szWinDir[MAX_PATH];
  87. if (GetWindowsDirectory(szWinDir, ARRAYSIZE(szWinDir)))
  88. {
  89. _bSysVolume = szWinDir[0] == _szRoot[0];
  90. }
  91. }
  92. CLowDiskSpaceUI::~CLowDiskSpaceUI()
  93. {
  94. if (_hMutex)
  95. CloseHandle(_hMutex);
  96. }
  97. HRESULT CLowDiskSpaceUI::QueryInterface(REFIID riid, void **ppv)
  98. {
  99. static const QITAB qit[] =
  100. {
  101. QITABENT(CLowDiskSpaceUI, IQueryContinue),
  102. { 0 },
  103. };
  104. return QISearch(this, qit, riid, ppv);
  105. }
  106. STDMETHODIMP_(ULONG) CLowDiskSpaceUI::AddRef()
  107. {
  108. return InterlockedIncrement(&_cRef);
  109. }
  110. STDMETHODIMP_(ULONG) CLowDiskSpaceUI::Release()
  111. {
  112. if (InterlockedDecrement(&_cRef))
  113. return _cRef;
  114. delete this;
  115. return 0;
  116. }
  117. HRESULT CLowDiskSpaceUI::QueryContinue()
  118. {
  119. LOWDISK_STATE ldsOld = _ldsCurrent;
  120. return ldsOld == _GetCurrentState(TRUE) ? S_OK : S_FALSE;
  121. }
  122. const STATE_DATA *CLowDiskSpaceUI::_StateData(LOWDISK_STATE lds)
  123. {
  124. for (int i = 0; i < ARRAYSIZE(c_state_data); i++)
  125. {
  126. if (c_state_data[i].lds == lds)
  127. return &c_state_data[i];
  128. }
  129. return NULL;
  130. }
  131. LOWDISK_STATE CLowDiskSpaceUI::_StateFromFreeSpace(ULARGE_INTEGER ulTotal, ULARGE_INTEGER ulFree)
  132. {
  133. ULONGLONG ulTotalMB = (ulTotal.QuadPart / BYTES_PER_MB);
  134. ULONGLONG ulFreeMB = (ulFree.QuadPart / BYTES_PER_MB);
  135. _dwLastFreeMB = (DWORD)ulFreeMB;
  136. for (int i = 0; i < ARRAYSIZE(c_state_data); i++)
  137. {
  138. // total needs to be 8 times the max of this range for us to consider it
  139. if ((ulTotalMB / 8) > c_state_data[i].dwMaxMB)
  140. {
  141. if ((c_state_data[i].lds == _ldsCurrent) ?
  142. ((ulFreeMB >= c_state_data[i].dwMinMB) && (ulFreeMB <= (c_state_data[i].dwMaxMB + 3))) :
  143. ((ulFreeMB >= c_state_data[i].dwMinMB) && (ulFreeMB <= c_state_data[i].dwMaxMB)))
  144. {
  145. // only report 200MB state on drives >= 2.25GB
  146. if ((c_state_data[i].lds != STATE_200MB) || (ulTotal.QuadPart >= (2250 * BYTES_PER_MB)))
  147. return c_state_data[i].lds;
  148. }
  149. }
  150. }
  151. return STATE_ALLGOOD;
  152. }
  153. LOWDISK_STATE CLowDiskSpaceUI::_GetCurrentState(BOOL bInStateMachine)
  154. {
  155. LOWDISK_STATE ldsNew = STATE_ALLGOOD; // assume this in case of failure
  156. ULARGE_INTEGER ulTotal, ulFree;
  157. if (SHGetDiskFreeSpaceEx(_szRoot, NULL, &ulTotal, &ulFree))
  158. {
  159. ldsNew = _StateFromFreeSpace(ulTotal, ulFree);
  160. }
  161. if (bInStateMachine)
  162. {
  163. if (_ldsCurrent != ldsNew)
  164. {
  165. // state change
  166. // if things are getting worse need to show UI (if we are in the state machine)
  167. _bShowUI = (ldsNew < _ldsCurrent);
  168. SRNotify(_szRoot, _dwLastFreeMB, ldsNew > _ldsCurrent); // call system restore
  169. }
  170. _ldsCurrent = ldsNew;
  171. }
  172. return ldsNew;
  173. }
  174. // creates the notification icon in the tray and shows a balloon with it
  175. // this is a modal call, when it returns either the UI has timed out or the
  176. // user has clicked on the notification UI.
  177. void CLowDiskSpaceUI::_DoNotificationUI()
  178. {
  179. // assume this will be a one shot UI, but this can get reset via our callback
  180. _bShowUI = FALSE;
  181. const STATE_DATA *psd = _StateData(_ldsCurrent);
  182. if (psd && (_bSysVolume || (psd->lds <= STATE_80MB)))
  183. {
  184. IUserNotification *pun;
  185. HRESULT hr = CoCreateInstance(CLSID_UserNotification, NULL, CLSCTX_ALL, IID_PPV_ARG(IUserNotification, &pun));
  186. if (SUCCEEDED(hr))
  187. {
  188. SHFILEINFO sfi = {0};
  189. SHGetFileInfo(_szRoot, FILE_ATTRIBUTE_DIRECTORY, &sfi, sizeof(sfi),
  190. SHGFI_USEFILEATTRIBUTES | SHGFI_DISPLAYNAME |
  191. SHGFI_ICON | SHGFI_SMALLICON | SHGFI_ADDOVERLAYS);
  192. TCHAR szTitle[64], szMsg[256], szTemplate[256];
  193. UINT niif = _bSysVolume ? psd->niif : NIIF_INFO;
  194. LoadString(HINST_THISDLL, IDS_DISK_FULL_TITLE, szTitle, ARRAYSIZE(szTitle));
  195. LoadString(HINST_THISDLL, NIIF_INFO == niif ? IDS_DISK_FULL_TEXT : IDS_DISK_FULL_TEXT_SERIOUS,
  196. szTemplate, ARRAYSIZE(szTemplate));
  197. wnsprintf(szMsg, ARRAYSIZE(szMsg), szTemplate, sfi.szDisplayName);
  198. pun->SetIconInfo(sfi.hIcon, szTitle);
  199. pun->SetBalloonRetry(psd->dwShowTime * 1000, psd->dwIntervalTime * 1000, psd->cRetry);
  200. // pun->SetBalloonInfo(szTitle, L"<a href=\"notepad.exe\"> Click here for notepad</a>", niif);
  201. pun->SetBalloonInfo(szTitle, szMsg, niif);
  202. hr = pun->Show(SAFECAST(this, IQueryContinue *), 1 * 1000); // 1 sec poll for callback
  203. if (sfi.hIcon)
  204. DestroyIcon(sfi.hIcon);
  205. if (S_OK == hr)
  206. {
  207. // S_OK -> user click on icon or balloon
  208. LaunchDiskCleanup(NULL, DRIVEID(_szRoot), (_bSysVolume ? psd->dwCleanupFlags : 0) | DISKCLEANUP_MODAL);
  209. }
  210. pun->Release();
  211. }
  212. }
  213. }
  214. void CLowDiskSpaceUI::_DoStateMachine()
  215. {
  216. do
  217. {
  218. if (_bShowUI)
  219. {
  220. _DoNotificationUI();
  221. }
  222. else
  223. {
  224. SHProcessMessagesUntilEvent(NULL, NULL, 5 * 1000); // 5 seconds
  225. }
  226. }
  227. while (STATE_ALLGOOD != _GetCurrentState(TRUE));
  228. }
  229. BOOL CLowDiskSpaceUI::_EnterExclusive()
  230. {
  231. if (NULL == _hMutex)
  232. {
  233. TCHAR szEvent[32];
  234. wsprintf(szEvent, TEXT("LowDiskOn%C"), _szRoot[0]);
  235. _hMutex = CreateMutex(CreateAllAccessSecurityAttributes(NULL, NULL, NULL), FALSE, szEvent);
  236. }
  237. return _hMutex && WAIT_OBJECT_0 == WaitForSingleObject(_hMutex, 0); // zero timeout
  238. }
  239. void CLowDiskSpaceUI::_LeaveExclusive()
  240. {
  241. ASSERT(_hMutex);
  242. ReleaseMutex(_hMutex);
  243. }
  244. DWORD CALLBACK CLowDiskSpaceUI::s_ThreadProc(void *pv)
  245. {
  246. CLowDiskSpaceUI *pldsui = (CLowDiskSpaceUI *)pv;
  247. if (pldsui->_EnterExclusive())
  248. {
  249. pldsui->_DoStateMachine();
  250. pldsui->_LeaveExclusive();
  251. }
  252. pldsui->Release();
  253. return 0;
  254. }
  255. void CLowDiskSpaceUI::CheckDiskSpace()
  256. {
  257. if (STATE_ALLGOOD != _GetCurrentState(FALSE))
  258. {
  259. AddRef();
  260. if (!SHCreateThread(s_ThreadProc, this, CTF_COINIT, NULL))
  261. {
  262. Release();
  263. }
  264. }
  265. }
  266. STDAPI CheckDiskSpace()
  267. {
  268. // the only caller of this is in explorer\tray.cpp
  269. // it checks against SHRestricted(REST_NOLOWDISKSPACECHECKS) on that side.
  270. for (int i = 0; i < 26; i++)
  271. {
  272. CMountPoint* pmp = CMountPoint::GetMountPoint(i);
  273. if (pmp)
  274. {
  275. if (pmp->IsFixedDisk() && !pmp->IsRemovableDevice())
  276. {
  277. CLowDiskSpaceUI *pldsui = new CLowDiskSpaceUI(i);
  278. if (pldsui)
  279. {
  280. pldsui->CheckDiskSpace();
  281. pldsui->Release();
  282. }
  283. }
  284. pmp->Release();
  285. }
  286. }
  287. return S_OK;
  288. }
  289. STDAPI_(BOOL) GetDiskCleanupPath(LPTSTR pszBuf, UINT cchSize)
  290. {
  291. if (pszBuf)
  292. *pszBuf = 0;
  293. DWORD cbLen = CbFromCch(cchSize);
  294. return SUCCEEDED(SKGetValue(SHELLKEY_HKLM_EXPLORER, TEXT("MyComputer\\cleanuppath"), NULL, NULL, pszBuf, &cbLen));
  295. }
  296. STDAPI_(void) LaunchDiskCleanup(HWND hwnd, int iDrive, UINT uFlags)
  297. {
  298. TCHAR szPathTemplate[MAX_PATH];
  299. if (GetDiskCleanupPath(szPathTemplate, ARRAYSIZE(szPathTemplate)))
  300. {
  301. TCHAR szPath[MAX_PATH], szArgs[MAX_PATH];
  302. wsprintf(szPath, szPathTemplate, TEXT('A') + iDrive);
  303. if (uFlags & DISKCLEANUP_LOWDISK)
  304. {
  305. lstrcatn(szPath, TEXT(" /LOWDISK"), ARRAYSIZE(szPath));
  306. }
  307. else if (uFlags & DISKCLEANUP_VERYLOWDISK)
  308. {
  309. lstrcatn(szPath, TEXT(" /VERYLOWDISK"), ARRAYSIZE(szPath));
  310. }
  311. PathSeperateArgs(szPath, szArgs);
  312. SHELLEXECUTEINFO ei =
  313. {
  314. sizeof(ei),
  315. SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS,
  316. NULL, NULL, szPath, szArgs, NULL, SW_SHOWNORMAL, NULL
  317. };
  318. if (ShellExecuteEx(&ei))
  319. {
  320. if (ei.hProcess)
  321. {
  322. if (DISKCLEANUP_MODAL & uFlags)
  323. SHProcessMessagesUntilEvent(NULL, ei.hProcess, INFINITE);
  324. CloseHandle(ei.hProcess);
  325. }
  326. }
  327. else
  328. {
  329. ShellMessageBox(HINST_THISDLL, NULL,
  330. MAKEINTRESOURCE(IDS_NO_CLEANMGR_APP),
  331. NULL, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
  332. }
  333. }
  334. }
  335. // public export
  336. STDAPI_(void) SHHandleDiskFull(HWND hwnd, int idDrive)
  337. {
  338. // legacy, no one calls this
  339. }