1 #include "Defragment.h" 2 3 4 // Ahh yes I ripped this from my old Findupes project :) 5 // Fits a path name, composed of a path (i.e. "c:\blah\blah\cha\cha") and a filename ("stuff.txt") 6 // and fits it to a given length. If it has to truncate it will first truncate from the path, 7 // substituting in periods. So you might end up with something like: 8 // C:\Program Files\Micro...\Register.exe 9 int FitName (wchar_t *destination, const wchar_t *path, const wchar_t *filename, uint32 totalWidth) 10 { 11 uint32 pathLen=0; 12 uint32 fnLen=0; 13 uint32 halfTotLen=0; 14 uint32 len4fn=0; /* number of chars remaining for filename after path is applied */ 15 uint32 len4path=0; /* number of chars for path before filename is applied */ 16 wchar_t fmtStrPath[20]=L""; 17 wchar_t fmtStrFile[20]=L""; 18 wchar_t fmtString[40]=L""; 19 20 /* 21 assert (destination != NULL); 22 assert (path != NULL); 23 assert (filename != NULL); 24 assert (totalWidth != 0); 25 */ 26 27 pathLen = wcslen(path); 28 fnLen = wcslen(filename); 29 if (!(totalWidth % 2)) 30 halfTotLen=totalWidth / 2; 31 else 32 halfTotLen=(totalWidth-1) / 2; /* -1 because otherwise (halfTotLen*2) == 33 (totalWidth+1) which wouldn't be good */ 34 35 /* determine how much width the path and filename each get */ 36 if ( (pathLen >= halfTotLen) && (fnLen < halfTotLen) ) 37 { 38 len4fn = fnLen; 39 len4path = (totalWidth - len4fn); 40 } 41 42 if ( (pathLen < halfTotLen) && (fnLen < halfTotLen) ) 43 { 44 len4fn = fnLen; 45 len4path = pathLen; 46 } 47 48 if ( (pathLen >= halfTotLen) && (fnLen >= halfTotLen) ) 49 { 50 len4fn = halfTotLen; 51 len4path = halfTotLen; 52 } 53 54 if ( (pathLen < halfTotLen) && (fnLen >= halfTotLen) ) 55 { 56 len4path = pathLen; 57 len4fn = (totalWidth - len4path); 58 } 59 /* 60 if halfTotLen was adjusted above to avoid a rounding error, give the 61 extra wchar_t to the filename 62 */ 63 if (halfTotLen < (totalWidth/2)) len4path++; 64 65 if (pathLen > len4path) swprintf (fmtStrPath, L"%%.%ds...\\", len4path-4); 66 else 67 swprintf (fmtStrPath, L"%%s"); 68 69 if (fnLen > len4fn) swprintf (fmtStrFile, L"%%.%ds...", len4fn-3); 70 else 71 swprintf (fmtStrFile, L"%%s"); 72 73 wcscpy (fmtString, fmtStrPath); 74 wcscat (fmtString, fmtStrFile); 75 /*swprintf (fmtString, L"%s%s", fmtStrPath, fmtStrFile);*/ 76 swprintf (destination, fmtString, path,filename); 77 78 return (1); 79 } 80 81 Defragment::Defragment (wstring Name, DefragType DefragMethod) 82 { 83 Method = DefragMethod; 84 DoLimitLength = true; 85 Error = false; 86 Done = false; 87 PleaseStop = false; 88 PleasePause = false; 89 DriveName = Name; 90 StatusPercent = 0.0f; 91 LastBMPUpdate = GetTickCount (); 92 93 SetStatusString (L"Opening volume " + Name); 94 if (!Volume.Open (Name)) 95 { 96 SetStatusString (L"Error opening volume " + Name); 97 Error = true; 98 Done = true; 99 StatusPercent = 100.0f; 100 } 101 102 return; 103 } 104 105 106 Defragment::~Defragment () 107 { 108 if (!IsDoneYet ()) 109 { 110 Stop (); 111 while (!IsDoneYet() && !HasError()) 112 { 113 SetStatusString (L"Waiting for thread to stop ..."); 114 Sleep (150); 115 } 116 } 117 118 Volume.Close (); 119 return; 120 } 121 122 123 void Defragment::SetStatusString (wstring NewStatus) 124 { 125 Lock (); 126 StatusString = NewStatus; 127 Unlock (); 128 129 return; 130 } 131 132 133 wstring Defragment::GetStatusString (void) 134 { 135 wstring ReturnVal; 136 137 Lock (); 138 ReturnVal = StatusString; 139 Unlock (); 140 141 return (ReturnVal); 142 } 143 144 145 double Defragment::GetStatusPercent (void) 146 { 147 return (StatusPercent); 148 } 149 150 151 bool Defragment::IsDoneYet (void) 152 { 153 return (Done); 154 } 155 156 157 void Defragment::Start (void) 158 { 159 uint32 i; 160 uint64 FirstFreeLCN; 161 uint64 TotalClusters; 162 uint64 ClustersProgress; 163 wchar_t PrintName[80]; 164 int Width = 70; 165 166 if (Error) 167 goto DoneDefrag; 168 169 // First thing: build a file list. 170 SetStatusString (L"Getting volume bitmap"); 171 if (!Volume.GetBitmap()) 172 { 173 SetStatusString (L"Could not get volume " + DriveName + L" bitmap"); 174 Error = true; 175 goto DoneDefrag; 176 } 177 178 LastBMPUpdate = GetTickCount (); 179 180 if (PleaseStop) 181 goto DoneDefrag; 182 183 SetStatusString (L"Obtaining volume geometry"); 184 if (!Volume.ObtainInfo ()) 185 { 186 SetStatusString (L"Could not obtain volume " + DriveName + L" geometry"); 187 Error = true; 188 goto DoneDefrag; 189 } 190 191 if (PleaseStop) 192 goto DoneDefrag; 193 194 SetStatusString (L"Building file database for volume " + DriveName); 195 if (!Volume.BuildFileList (PleaseStop, StatusPercent)) 196 { 197 SetStatusString (L"Could not build file database for volume " + DriveName); 198 Error = true; 199 goto DoneDefrag; 200 } 201 202 if (PleaseStop) 203 goto DoneDefrag; 204 205 SetStatusString (L"Analyzing database for " + DriveName); 206 TotalClusters = 0; 207 for (i = 0; i < Volume.GetDBFileCount(); i++) 208 { 209 TotalClusters += Volume.GetDBFile(i).Clusters; 210 } 211 212 // Defragment! 213 ClustersProgress = 0; 214 215 // Find first free LCN for speedier searches ... 216 Volume.FindFreeRange (0, 1, FirstFreeLCN); 217 218 if (PleaseStop) 219 goto DoneDefrag; 220 221 // Analyze? 222 if (Method == DefragAnalyze) 223 { 224 uint32 j; 225 226 Report.RootPath = Volume.GetRootPath (); 227 228 Report.FraggedFiles.clear (); 229 Report.UnfraggedFiles.clear (); 230 Report.UnmovableFiles.clear (); 231 232 Report.FilesCount = Volume.GetDBFileCount () - Volume.GetDBDirCount (); 233 Report.DirsCount = Volume.GetDBDirCount (); 234 Report.DiskSizeBytes = Volume.GetVolumeInfo().TotalBytes; 235 236 Report.FilesSizeClusters = 0; 237 Report.FilesSlackBytes = 0; 238 Report.FilesSizeBytes = 0; 239 Report.FilesFragments = 0; 240 241 for (j = 0; j < Volume.GetDBFileCount(); j++) 242 { 243 FileInfo Info; 244 245 Info = Volume.GetDBFile (j); 246 247 Report.FilesFragments += max ((size_t)1, Info.Fragments.size()); // add 1 fragment even for 0 bytes/0 cluster files 248 249 if (Info.Attributes.Process == 0) 250 continue; 251 252 SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name); 253 254 Report.FilesSizeClusters += Info.Clusters; 255 Report.FilesSizeBytes += Info.Size; 256 257 if (Info.Attributes.Unmovable == 1) 258 Report.UnmovableFiles.push_back (j); 259 260 if (Info.Fragments.size() > 1) 261 Report.FraggedFiles.push_back (j); 262 else 263 Report.UnfraggedFiles.push_back (j); 264 265 StatusPercent = ((double)j / (double)Report.FilesCount) * 100.0f; 266 } 267 268 Report.FilesSizeOnDisk = Report.FilesSizeClusters * (uint64)Volume.GetVolumeInfo().ClusterSize; 269 Report.FilesSlackBytes = Report.FilesSizeOnDisk - Report.FilesSizeBytes; 270 Report.AverageFragments = (double)Report.FilesFragments / (double)Report.FilesCount; 271 Report.PercentFragged = 100.0f * ((double)(signed)Report.FraggedFiles.size() / (double)(signed)Report.FilesCount); 272 273 uint64 Percent; 274 Percent = (10000 * Report.FilesSlackBytes) / Report.FilesSizeOnDisk; 275 Report.PercentSlack = (double)(signed)Percent / 100.0f; 276 } 277 else 278 // Go through all the files and ... defragment them! 279 for (i = 0; i < Volume.GetDBFileCount(); i++) 280 { 281 FileInfo Info; 282 bool Result; 283 uint64 TargetLCN; 284 uint64 PreviousClusters; 285 286 // What? They want us to pause? Oh ok. 287 if (PleasePause) 288 { 289 SetStatusString (L"Paused"); 290 PleasePause = false; 291 292 while (PleasePause == false) 293 { 294 Sleep (50); 295 } 296 297 PleasePause = false; 298 } 299 300 if (PleaseStop) 301 { 302 SetStatusString (L"Stopping"); 303 break; 304 } 305 306 // 307 Info = Volume.GetDBFile (i); 308 309 PreviousClusters = ClustersProgress; 310 ClustersProgress += Info.Clusters; 311 312 if (Info.Attributes.Process == 0) 313 continue; 314 315 if (!DoLimitLength) 316 SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name); 317 else 318 { 319 FitName (PrintName, Volume.GetDBDir (Info.DirIndice).c_str(), Info.Name.c_str(), Width); 320 SetStatusString (PrintName); 321 } 322 323 // Calculate percentage complete 324 StatusPercent = 100.0f * double((double)PreviousClusters / (double)TotalClusters); 325 326 // Can't defrag directories yet 327 if (Info.Attributes.Directory == 1) 328 continue; 329 330 // Can't defrag 0 byte files :) 331 if (Info.Fragments.empty()) 332 continue; 333 334 // If doing fast defrag, skip non-fragmented files 335 // Note: This assumes that the extents stored in Info.Fragments 336 // are consolidated. I.e. we assume it is NOT the case that 337 // two extents account for a sequential range of (non- 338 // fragmented) clusters. 339 if (Info.Fragments.size() == 1 && Method == DefragFast) 340 continue; 341 342 // Otherwise, defrag0rize it! 343 int Retry = 3; // retry a few times 344 while (Retry > 0) 345 { 346 // Find a place that can fit the file 347 Result = Volume.FindFreeRange (FirstFreeLCN, Info.Clusters, TargetLCN); 348 349 // If yes, try moving it 350 if (Result) 351 { 352 // If we're doing an extensive defrag and the file is already defragmented 353 // and if its new location would be after its current location, don't 354 // move it. 355 if (Method == DefragExtensive && Info.Fragments.size() == 1 && 356 TargetLCN > Info.Fragments[0].StartLCN) 357 { 358 Retry = 1; 359 } 360 else 361 { 362 if (Volume.MoveFileDumb (i, TargetLCN)) 363 { 364 Retry = 1; // yay, all done with this file. 365 Volume.FindFreeRange (0, 1, FirstFreeLCN); 366 } 367 } 368 } 369 370 // New: Only update bitmap if it's older than 15 seconds 371 if ((GetTickCount() - LastBMPUpdate) < 15000) 372 Retry = 1; 373 else 374 if (!Result || Retry != 1) 375 { // hmm. Wait for a moment, then update the drive bitmap 376 //SetStatusString (L"(Reobtaining volume " + DriveName + L" bitmap)"); 377 378 if (!DoLimitLength) 379 { 380 SetStatusString (GetStatusString() + wstring (L" .")); 381 } 382 383 if (Volume.GetBitmap ()) 384 { 385 LastBMPUpdate = GetTickCount (); 386 387 if (!DoLimitLength) 388 SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name); 389 else 390 SetStatusString (PrintName); 391 392 Volume.FindFreeRange (0, 1, FirstFreeLCN); 393 } 394 else 395 { 396 SetStatusString (L"Could not re-obtain volume " + DriveName + L" bitmap"); 397 Error = true; 398 } 399 } 400 401 Retry--; 402 } 403 404 if (Error == true) 405 break; 406 } 407 408 DoneDefrag: 409 wstring OldStatus; 410 411 OldStatus = GetStatusString (); 412 StatusPercent = 99.999999f; 413 SetStatusString (L"Closing volume " + DriveName); 414 Volume.Close (); 415 StatusPercent = 100.0f; 416 417 // If there was an error then the wstring has already been set 418 if (Error) 419 SetStatusString (OldStatus); 420 else 421 if (PleaseStop) 422 SetStatusString (L"Volume " + DriveName + L" defragmentation was stopped"); 423 else 424 SetStatusString (L"Finished defragmenting " + DriveName); 425 426 Done = true; 427 428 return; 429 } 430 431 432 void Defragment::TogglePause (void) 433 { 434 Lock (); 435 SetStatusString (L"Pausing ..."); 436 PleasePause = true; 437 Unlock (); 438 439 return; 440 } 441 442 443 void Defragment::Stop (void) 444 { 445 Lock (); 446 SetStatusString (L"Stopping ..."); 447 PleaseStop = true; 448 Unlock (); 449 450 return; 451 } 452 453 454 bool Defragment::HasError (void) 455 { 456 return (Error); 457 } 458 459