mirror of
https://github.com/reactos/reactos.git
synced 2024-11-20 14:30:57 +00:00
458 lines
12 KiB
C++
458 lines
12 KiB
C++
#include "Defragment.h"
|
|
|
|
|
|
// Ahh yes I ripped this from my old Findupes project :)
|
|
// Fits a path name, composed of a path (i.e. "c:\blah\blah\cha\cha") and a filename ("stuff.txt")
|
|
// and fits it to a given length. If it has to truncate it will first truncate from the path,
|
|
// substituting in periods. So you might end up with something like:
|
|
// C:\Program Files\Micro...\Register.exe
|
|
int FitName (wchar_t *destination, const wchar_t *path, const wchar_t *filename, uint32 totalWidth)
|
|
{
|
|
uint32 pathLen=0;
|
|
uint32 fnLen=0;
|
|
uint32 halfTotLen=0;
|
|
uint32 len4fn=0; /* number of chars remaining for filename after path is applied */
|
|
uint32 len4path=0; /* number of chars for path before filename is applied */
|
|
wchar_t fmtStrPath[20]=L"";
|
|
wchar_t fmtStrFile[20]=L"";
|
|
wchar_t fmtString[40]=L"";
|
|
|
|
/*
|
|
assert (destination != NULL);
|
|
assert (path != NULL);
|
|
assert (filename != NULL);
|
|
assert (totalWidth != 0);
|
|
*/
|
|
|
|
pathLen = wcslen(path);
|
|
fnLen = wcslen(filename);
|
|
if (!(totalWidth % 2))
|
|
halfTotLen=totalWidth / 2;
|
|
else
|
|
halfTotLen=(totalWidth-1) / 2; /* -1 because otherwise (halfTotLen*2) ==
|
|
(totalWidth+1) which wouldn't be good */
|
|
|
|
/* determine how much width the path and filename each get */
|
|
if ( (pathLen >= halfTotLen) && (fnLen < halfTotLen) )
|
|
{
|
|
len4fn = fnLen;
|
|
len4path = (totalWidth - len4fn);
|
|
}
|
|
|
|
if ( (pathLen < halfTotLen) && (fnLen < halfTotLen) )
|
|
{
|
|
len4fn = fnLen;
|
|
len4path = pathLen;
|
|
}
|
|
|
|
if ( (pathLen >= halfTotLen) && (fnLen >= halfTotLen) )
|
|
{
|
|
len4fn = halfTotLen;
|
|
len4path = halfTotLen;
|
|
}
|
|
|
|
if ( (pathLen < halfTotLen) && (fnLen >= halfTotLen) )
|
|
{
|
|
len4path = pathLen;
|
|
len4fn = (totalWidth - len4path);
|
|
}
|
|
/*
|
|
if halfTotLen was adjusted above to avoid a rounding error, give the
|
|
extra wchar_t to the filename
|
|
*/
|
|
if (halfTotLen < (totalWidth/2)) len4path++;
|
|
|
|
if (pathLen > len4path) swprintf (fmtStrPath, L"%%.%ds...\\", len4path-4);
|
|
else
|
|
swprintf (fmtStrPath, L"%%s");
|
|
|
|
if (fnLen > len4fn) swprintf (fmtStrFile, L"%%.%ds...", len4fn-3);
|
|
else
|
|
swprintf (fmtStrFile, L"%%s");
|
|
|
|
wcscpy (fmtString, fmtStrPath);
|
|
wcscat (fmtString, fmtStrFile);
|
|
/*swprintf (fmtString, L"%s%s", fmtStrPath, fmtStrFile);*/
|
|
swprintf (destination, fmtString, path,filename);
|
|
|
|
return (1);
|
|
}
|
|
|
|
Defragment::Defragment (wstring Name, DefragType DefragMethod)
|
|
{
|
|
Method = DefragMethod;
|
|
DoLimitLength = true;
|
|
Error = false;
|
|
Done = false;
|
|
PleaseStop = false;
|
|
PleasePause = false;
|
|
DriveName = Name;
|
|
StatusPercent = 0.0f;
|
|
LastBMPUpdate = GetTickCount ();
|
|
|
|
SetStatusString (L"Opening volume " + Name);
|
|
if (!Volume.Open (Name))
|
|
{
|
|
SetStatusString (L"Error opening volume " + Name);
|
|
Error = true;
|
|
Done = true;
|
|
StatusPercent = 100.0f;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
Defragment::~Defragment ()
|
|
{
|
|
if (!IsDoneYet ())
|
|
{
|
|
Stop ();
|
|
while (!IsDoneYet() && !HasError())
|
|
{
|
|
SetStatusString (L"Waiting for thread to stop ...");
|
|
Sleep (150);
|
|
}
|
|
}
|
|
|
|
Volume.Close ();
|
|
return;
|
|
}
|
|
|
|
|
|
void Defragment::SetStatusString (wstring NewStatus)
|
|
{
|
|
Lock ();
|
|
StatusString = NewStatus;
|
|
Unlock ();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
wstring Defragment::GetStatusString (void)
|
|
{
|
|
wstring ReturnVal;
|
|
|
|
Lock ();
|
|
ReturnVal = StatusString;
|
|
Unlock ();
|
|
|
|
return (ReturnVal);
|
|
}
|
|
|
|
|
|
double Defragment::GetStatusPercent (void)
|
|
{
|
|
return (StatusPercent);
|
|
}
|
|
|
|
|
|
bool Defragment::IsDoneYet (void)
|
|
{
|
|
return (Done);
|
|
}
|
|
|
|
|
|
void Defragment::Start (void)
|
|
{
|
|
uint32 i;
|
|
uint64 FirstFreeLCN;
|
|
uint64 TotalClusters;
|
|
uint64 ClustersProgress;
|
|
wchar_t PrintName[80];
|
|
int Width = 70;
|
|
|
|
if (Error)
|
|
goto DoneDefrag;
|
|
|
|
// First thing: build a file list.
|
|
SetStatusString (L"Getting volume bitmap");
|
|
if (!Volume.GetBitmap())
|
|
{
|
|
SetStatusString (L"Could not get volume " + DriveName + L" bitmap");
|
|
Error = true;
|
|
goto DoneDefrag;
|
|
}
|
|
|
|
LastBMPUpdate = GetTickCount ();
|
|
|
|
if (PleaseStop)
|
|
goto DoneDefrag;
|
|
|
|
SetStatusString (L"Obtaining volume geometry");
|
|
if (!Volume.ObtainInfo ())
|
|
{
|
|
SetStatusString (L"Could not obtain volume " + DriveName + L" geometry");
|
|
Error = true;
|
|
goto DoneDefrag;
|
|
}
|
|
|
|
if (PleaseStop)
|
|
goto DoneDefrag;
|
|
|
|
SetStatusString (L"Building file database for volume " + DriveName);
|
|
if (!Volume.BuildFileList (PleaseStop, StatusPercent))
|
|
{
|
|
SetStatusString (L"Could not build file database for volume " + DriveName);
|
|
Error = true;
|
|
goto DoneDefrag;
|
|
}
|
|
|
|
if (PleaseStop)
|
|
goto DoneDefrag;
|
|
|
|
SetStatusString (L"Analyzing database for " + DriveName);
|
|
TotalClusters = 0;
|
|
for (i = 0; i < Volume.GetDBFileCount(); i++)
|
|
{
|
|
TotalClusters += Volume.GetDBFile(i).Clusters;
|
|
}
|
|
|
|
// Defragment!
|
|
ClustersProgress = 0;
|
|
|
|
// Find first free LCN for speedier searches ...
|
|
Volume.FindFreeRange (0, 1, FirstFreeLCN);
|
|
|
|
if (PleaseStop)
|
|
goto DoneDefrag;
|
|
|
|
// Analyze?
|
|
if (Method == DefragAnalyze)
|
|
{
|
|
uint32 j;
|
|
|
|
Report.RootPath = Volume.GetRootPath ();
|
|
|
|
Report.FraggedFiles.clear ();
|
|
Report.UnfraggedFiles.clear ();
|
|
Report.UnmovableFiles.clear ();
|
|
|
|
Report.FilesCount = Volume.GetDBFileCount () - Volume.GetDBDirCount ();
|
|
Report.DirsCount = Volume.GetDBDirCount ();
|
|
Report.DiskSizeBytes = Volume.GetVolumeInfo().TotalBytes;
|
|
|
|
Report.FilesSizeClusters = 0;
|
|
Report.FilesSlackBytes = 0;
|
|
Report.FilesSizeBytes = 0;
|
|
Report.FilesFragments = 0;
|
|
|
|
for (j = 0; j < Volume.GetDBFileCount(); j++)
|
|
{
|
|
FileInfo Info;
|
|
|
|
Info = Volume.GetDBFile (j);
|
|
|
|
Report.FilesFragments += max ((size_t)1, Info.Fragments.size()); // add 1 fragment even for 0 bytes/0 cluster files
|
|
|
|
if (Info.Attributes.Process == 0)
|
|
continue;
|
|
|
|
SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name);
|
|
|
|
Report.FilesSizeClusters += Info.Clusters;
|
|
Report.FilesSizeBytes += Info.Size;
|
|
|
|
if (Info.Attributes.Unmovable == 1)
|
|
Report.UnmovableFiles.push_back (j);
|
|
|
|
if (Info.Fragments.size() > 1)
|
|
Report.FraggedFiles.push_back (j);
|
|
else
|
|
Report.UnfraggedFiles.push_back (j);
|
|
|
|
StatusPercent = ((double)j / (double)Report.FilesCount) * 100.0f;
|
|
}
|
|
|
|
Report.FilesSizeOnDisk = Report.FilesSizeClusters * (uint64)Volume.GetVolumeInfo().ClusterSize;
|
|
Report.FilesSlackBytes = Report.FilesSizeOnDisk - Report.FilesSizeBytes;
|
|
Report.AverageFragments = (double)Report.FilesFragments / (double)Report.FilesCount;
|
|
Report.PercentFragged = 100.0f * ((double)(signed)Report.FraggedFiles.size() / (double)(signed)Report.FilesCount);
|
|
|
|
uint64 Percent;
|
|
Percent = (10000 * Report.FilesSlackBytes) / Report.FilesSizeOnDisk;
|
|
Report.PercentSlack = (double)(signed)Percent / 100.0f;
|
|
}
|
|
else
|
|
// Go through all the files and ... defragment them!
|
|
for (i = 0; i < Volume.GetDBFileCount(); i++)
|
|
{
|
|
FileInfo Info;
|
|
bool Result;
|
|
uint64 TargetLCN;
|
|
uint64 PreviousClusters;
|
|
|
|
// What? They want us to pause? Oh ok.
|
|
if (PleasePause)
|
|
{
|
|
SetStatusString (L"Paused");
|
|
PleasePause = false;
|
|
|
|
while (PleasePause == false)
|
|
{
|
|
Sleep (50);
|
|
}
|
|
|
|
PleasePause = false;
|
|
}
|
|
|
|
if (PleaseStop)
|
|
{
|
|
SetStatusString (L"Stopping");
|
|
break;
|
|
}
|
|
|
|
//
|
|
Info = Volume.GetDBFile (i);
|
|
|
|
PreviousClusters = ClustersProgress;
|
|
ClustersProgress += Info.Clusters;
|
|
|
|
if (Info.Attributes.Process == 0)
|
|
continue;
|
|
|
|
if (!DoLimitLength)
|
|
SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name);
|
|
else
|
|
{
|
|
FitName (PrintName, Volume.GetDBDir (Info.DirIndice).c_str(), Info.Name.c_str(), Width);
|
|
SetStatusString (PrintName);
|
|
}
|
|
|
|
// Calculate percentage complete
|
|
StatusPercent = 100.0f * double((double)PreviousClusters / (double)TotalClusters);
|
|
|
|
// Can't defrag directories yet
|
|
if (Info.Attributes.Directory == 1)
|
|
continue;
|
|
|
|
// Can't defrag 0 byte files :)
|
|
if (Info.Fragments.empty())
|
|
continue;
|
|
|
|
// If doing fast defrag, skip non-fragmented files
|
|
// Note: This assumes that the extents stored in Info.Fragments
|
|
// are consolidated. I.e. we assume it is NOT the case that
|
|
// two extents account for a sequential range of (non-
|
|
// fragmented) clusters.
|
|
if (Info.Fragments.size() == 1 && Method == DefragFast)
|
|
continue;
|
|
|
|
// Otherwise, defrag0rize it!
|
|
int Retry = 3; // retry a few times
|
|
while (Retry > 0)
|
|
{
|
|
// Find a place that can fit the file
|
|
Result = Volume.FindFreeRange (FirstFreeLCN, Info.Clusters, TargetLCN);
|
|
|
|
// If yes, try moving it
|
|
if (Result)
|
|
{
|
|
// If we're doing an extensive defrag and the file is already defragmented
|
|
// and if its new location would be after its current location, don't
|
|
// move it.
|
|
if (Method == DefragExtensive && Info.Fragments.size() == 1 &&
|
|
TargetLCN > Info.Fragments[0].StartLCN)
|
|
{
|
|
Retry = 1;
|
|
}
|
|
else
|
|
{
|
|
if (Volume.MoveFileDumb (i, TargetLCN))
|
|
{
|
|
Retry = 1; // yay, all done with this file.
|
|
Volume.FindFreeRange (0, 1, FirstFreeLCN);
|
|
}
|
|
}
|
|
}
|
|
|
|
// New: Only update bitmap if it's older than 15 seconds
|
|
if ((GetTickCount() - LastBMPUpdate) < 15000)
|
|
Retry = 1;
|
|
else
|
|
if (!Result || Retry != 1)
|
|
{ // hmm. Wait for a moment, then update the drive bitmap
|
|
//SetStatusString (L"(Reobtaining volume " + DriveName + L" bitmap)");
|
|
|
|
if (!DoLimitLength)
|
|
{
|
|
SetStatusString (GetStatusString() + wstring (L" ."));
|
|
}
|
|
|
|
if (Volume.GetBitmap ())
|
|
{
|
|
LastBMPUpdate = GetTickCount ();
|
|
|
|
if (!DoLimitLength)
|
|
SetStatusString (Volume.GetDBDir (Info.DirIndice) + Info.Name);
|
|
else
|
|
SetStatusString (PrintName);
|
|
|
|
Volume.FindFreeRange (0, 1, FirstFreeLCN);
|
|
}
|
|
else
|
|
{
|
|
SetStatusString (L"Could not re-obtain volume " + DriveName + L" bitmap");
|
|
Error = true;
|
|
}
|
|
}
|
|
|
|
Retry--;
|
|
}
|
|
|
|
if (Error == true)
|
|
break;
|
|
}
|
|
|
|
DoneDefrag:
|
|
wstring OldStatus;
|
|
|
|
OldStatus = GetStatusString ();
|
|
StatusPercent = 99.999999f;
|
|
SetStatusString (L"Closing volume " + DriveName);
|
|
Volume.Close ();
|
|
StatusPercent = 100.0f;
|
|
|
|
// If there was an error then the wstring has already been set
|
|
if (Error)
|
|
SetStatusString (OldStatus);
|
|
else
|
|
if (PleaseStop)
|
|
SetStatusString (L"Volume " + DriveName + L" defragmentation was stopped");
|
|
else
|
|
SetStatusString (L"Finished defragmenting " + DriveName);
|
|
|
|
Done = true;
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void Defragment::TogglePause (void)
|
|
{
|
|
Lock ();
|
|
SetStatusString (L"Pausing ...");
|
|
PleasePause = true;
|
|
Unlock ();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
void Defragment::Stop (void)
|
|
{
|
|
Lock ();
|
|
SetStatusString (L"Stopping ...");
|
|
PleaseStop = true;
|
|
Unlock ();
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
bool Defragment::HasError (void)
|
|
{
|
|
return (Error);
|
|
}
|
|
|