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
FitName(wchar_t * destination,const wchar_t * path,const wchar_t * filename,uint32 totalWidth)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 
Defragment(wstring Name,DefragType DefragMethod)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 
~Defragment()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 
SetStatusString(wstring NewStatus)123 void Defragment::SetStatusString (wstring NewStatus)
124 {
125     Lock ();
126     StatusString = NewStatus;
127     Unlock ();
128 
129     return;
130 }
131 
132 
GetStatusString(void)133 wstring Defragment::GetStatusString (void)
134 {
135     wstring ReturnVal;
136 
137     Lock ();
138     ReturnVal = StatusString;
139     Unlock ();
140 
141     return (ReturnVal);
142 }
143 
144 
GetStatusPercent(void)145 double Defragment::GetStatusPercent (void)
146 {
147     return (StatusPercent);
148 }
149 
150 
IsDoneYet(void)151 bool Defragment::IsDoneYet (void)
152 {
153     return (Done);
154 }
155 
156 
Start(void)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 
TogglePause(void)432 void Defragment::TogglePause (void)
433 {
434     Lock ();
435     SetStatusString (L"Pausing ...");
436     PleasePause = true;
437     Unlock ();
438 
439     return;
440 }
441 
442 
Stop(void)443 void Defragment::Stop (void)
444 {
445     Lock ();
446     SetStatusString (L"Stopping ...");
447     PleaseStop = true;
448     Unlock ();
449 
450     return;
451 }
452 
453 
HasError(void)454 bool Defragment::HasError (void)
455 {
456     return (Error);
457 }
458 
459