xref: /haiku/src/apps/diskusage/Scanner.cpp (revision af960311c029f40bf5fbb7ced8db7fbf919d6880)
1 /*
2  * Copyright (c) 2008 Stephan Aßmus <superstippi@gmx.de>. All rights reserved.
3  * Distributed under the terms of the MIT/X11 license.
4  *
5  * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software
6  * as long as it is accompanied by it's documentation and this copyright notice.
7  * The software comes with no warranty, etc.
8  */
9 
10 
11 #include "Scanner.h"
12 
13 #include <stdlib.h>
14 #include <string.h>
15 
16 #include <Catalog.h>
17 #include <Directory.h>
18 
19 #include "DiskUsage.h"
20 
21 #undef B_TRANSLATION_CONTEXT
22 #define B_TRANSLATION_CONTEXT "Scanner"
23 
24 using std::vector;
25 
26 
Scanner(BVolume * v,BHandler * handler)27 Scanner::Scanner(BVolume *v, BHandler *handler)
28 	:
29 	BLooper(),
30 	fListener(handler),
31 	fDoneMessage(kScanDone),
32 	fProgressMessage(kScanProgress),
33 	fVolume(v),
34 	fSnapshot(NULL),
35 	fDesiredPath(),
36 	fTask(),
37 	fBusy(false),
38 	fQuitRequested(false)
39 {
40 	Run();
41 }
42 
43 
~Scanner()44 Scanner::~Scanner()
45 {
46 	delete fSnapshot;
47 }
48 
49 
50 void
MessageReceived(BMessage * message)51 Scanner::MessageReceived(BMessage* message)
52 {
53 	switch (message->what) {
54 		case kScanRefresh:
55 		{
56 			FileInfo* startInfo;
57 			if (message->FindPointer(kNameFilePtr, (void **)&startInfo)
58 				== B_OK)
59 				_RunScan(startInfo);
60 			break;
61 		}
62 
63 		default:
64 			BLooper::MessageReceived(message);
65 			break;
66 	}
67 }
68 
69 
70 void
Refresh(FileInfo * startInfo)71 Scanner::Refresh(FileInfo* startInfo)
72 {
73 	if (fBusy)
74 		return;
75 
76 	fBusy = true;
77 
78 	// Remember the current directory, if any, so we can return to it after
79 	// the scanning is done.
80 	if (fSnapshot != NULL && fSnapshot->currentDir != NULL)
81 		fSnapshot->currentDir->GetPath(fDesiredPath);
82 
83 	fTask.assign(kEmptyStr);
84 
85 	BMessage msg(kScanRefresh);
86 	msg.AddPointer(kNameFilePtr, startInfo);
87 	PostMessage(&msg);
88 }
89 
90 
91 void
Cancel()92 Scanner::Cancel()
93 {
94 	if (!fBusy)
95 		return;
96 
97 	fQuitRequested = true;
98 }
99 
100 
101 void
SetDesiredPath(string & path)102 Scanner::SetDesiredPath(string &path)
103 {
104 	fDesiredPath = path;
105 
106 	if (fSnapshot == NULL)
107 		Refresh();
108 	else
109 		_ChangeToDesired();
110 }
111 
112 
113 void
RequestQuit()114 Scanner::RequestQuit()
115 {
116 	// If the looper thread is scanning, we won't succeed to grab the lock,
117 	// since the thread is scanning from within MessageReceived(). Then by
118 	// setting fQuitRequested, the thread will stop scanning and only after
119 	// that happened we succeed to grab the lock. So this line of action
120 	// should be safe.
121 	fQuitRequested = true;
122 	Lock();
123 	Quit();
124 }
125 
126 
127 // #pragma mark - private
128 
129 
130 bool
_DirectoryContains(FileInfo * currentDir,entry_ref * ref)131 Scanner::_DirectoryContains(FileInfo* currentDir, entry_ref* ref)
132 {
133 	vector<FileInfo*>::iterator i = currentDir->children.begin();
134 	bool contains = currentDir->ref == *ref;
135 	while (!contains && i != currentDir->children.end()) {
136 		contains |= _DirectoryContains((*i), ref);
137 		i++;
138 	}
139 	return contains;
140 }
141 
142 
143 void
_RunScan(FileInfo * startInfo)144 Scanner::_RunScan(FileInfo* startInfo)
145 {
146 	fQuitRequested = false;
147 	BString stringScan(B_TRANSLATE("Scanning %refName%"));
148 
149 	if (startInfo == NULL || startInfo == fSnapshot->rootDir) {
150 		VolumeSnapshot* previousSnapshot = fSnapshot;
151 		fSnapshot = new VolumeSnapshot(fVolume);
152 		stringScan.ReplaceFirst("%refName%", fSnapshot->name.c_str());
153 		fTask = stringScan.String();
154 		fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes;
155 		fVolumeBytesScanned = 0;
156 		fProgress = 0.0;
157 		fLastReport = -1.0;
158 
159 		BDirectory root;
160 		fVolume->GetRootDirectory(&root);
161 		fSnapshot->rootDir = _GetFileInfo(&root, NULL);
162 		if (fSnapshot->rootDir == NULL) {
163 			delete fSnapshot;
164 			fSnapshot = previousSnapshot;
165 			fBusy = false;
166 			fListener.SendMessage(&fDoneMessage);
167 			return;
168 		}
169 		FileInfo* freeSpace = new FileInfo;
170 		freeSpace->pseudo = true;
171 		BString string(B_TRANSLATE("Free on %refName%"));
172 		string.ReplaceFirst("%refName%", fSnapshot->name.c_str());
173 		freeSpace->ref.set_name(string.String());
174 		freeSpace->size = fSnapshot->freeBytes;
175 		fSnapshot->freeSpace = freeSpace;
176 
177 		fSnapshot->currentDir = NULL;
178 
179 		delete previousSnapshot;
180 	} else {
181 		off_t previousVolumeCapacity = fSnapshot->capacity;
182 		off_t previousVolumeFreeBytes = fSnapshot->freeBytes;
183 		fSnapshot->capacity = fVolume->Capacity();
184 		fSnapshot->freeBytes = fVolume->FreeBytes();
185 		stringScan.ReplaceFirst("%refName%", startInfo->ref.name);
186 		fTask = stringScan.String();
187 		fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes;
188 		fVolumeBytesScanned = fVolumeBytesInUse - startInfo->size; //best guess
189 		fProgress = fVolumeBytesScanned / fVolumeBytesInUse;
190 		fLastReport = -1.0;
191 
192 		BDirectory startDir(&startInfo->ref);
193 		if (startDir.InitCheck() == B_OK) {
194 			FileInfo *parent = startInfo->parent;
195 			vector<FileInfo *>::iterator i = parent->children.begin();
196 			FileInfo* newInfo = _GetFileInfo(&startDir, parent);
197 			if (newInfo == NULL) {
198 				fSnapshot->capacity = previousVolumeCapacity;
199 				fSnapshot->freeBytes = previousVolumeFreeBytes;
200 				fBusy = false;
201 				fListener.SendMessage(&fDoneMessage);
202 				return;
203 			}
204 			while (i != parent->children.end() && *i != startInfo)
205 				i++;
206 
207 			int idx = i - parent->children.begin();
208 			parent->children[idx] = newInfo;
209 
210 			// Fixup count and size fields in parent directory.
211 			off_t sizeDiff = newInfo->size - startInfo->size;
212 			off_t countDiff = newInfo->count - startInfo->count;
213 			while (parent != NULL) {
214 				parent->size += sizeDiff;
215 				parent->count += countDiff;
216 				parent = parent->parent;
217 			}
218 
219 			delete startInfo;
220 		}
221 	}
222 	fBusy = false;
223 	_ChangeToDesired();
224 	fListener.SendMessage(&fDoneMessage);
225 }
226 
227 
228 FileInfo*
_GetFileInfo(BDirectory * dir,FileInfo * parent)229 Scanner::_GetFileInfo(BDirectory* dir, FileInfo* parent)
230 {
231 	FileInfo* thisDir = new FileInfo;
232 	BEntry entry;
233 	dir->GetEntry(&entry);
234 	entry.GetRef(&thisDir->ref);
235 	thisDir->parent = parent;
236 	thisDir->count = 0;
237 
238 	while (true) {
239 		if (fQuitRequested) {
240 			delete thisDir;
241 			return NULL;
242 		}
243 
244 		if (dir->GetNextEntry(&entry) == B_ENTRY_NOT_FOUND)
245 			break;
246 		if (entry.IsSymLink())
247 			continue;
248 
249 
250 		if (entry.IsFile()) {
251 			entry_ref ref;
252 			if ((entry.GetRef(&ref) == B_OK) && (ref.device != Device()))
253 				continue;
254 			FileInfo *child = new FileInfo;
255 			entry.GetRef(&child->ref);
256 			entry.GetSize(&child->size);
257 			child->parent = thisDir;
258 			child->color = -1;
259 			thisDir->children.push_back(child);
260 
261 			// Send a progress report periodically.
262 			fVolumeBytesScanned += child->size;
263 			fProgress = (float)fVolumeBytesScanned / fVolumeBytesInUse;
264 			if (fProgress - fLastReport > kReportInterval) {
265 				fLastReport = fProgress;
266 				fListener.SendMessage(&fProgressMessage);
267 			}
268 		}
269 		else if (entry.IsDirectory()) {
270 			BDirectory childDir(&entry);
271 			thisDir->children.push_back(_GetFileInfo(&childDir, thisDir));
272 		}
273 		thisDir->count++;
274 	}
275 
276 	vector<FileInfo *>::iterator i = thisDir->children.begin();
277 	while (i != thisDir->children.end()) {
278 		thisDir->size += (*i)->size;
279 		thisDir->count += (*i)->count;
280 		i++;
281 	}
282 
283 	return thisDir;
284 }
285 
286 
287 void
_ChangeToDesired()288 Scanner::_ChangeToDesired()
289 {
290 	if (fBusy || fDesiredPath.size() <= 0)
291 		return;
292 
293 	char* workPath = strdup(fDesiredPath.c_str());
294 	char* pathPtr = &workPath[1]; // skip leading '/'
295 	char* endOfPath = pathPtr + strlen(pathPtr);
296 
297 	// Check the name of the root directory.  It is a special case because
298 	// it has no parent data structure.
299 	FileInfo* checkDir = fSnapshot->rootDir;
300 	FileInfo* prefDir = NULL;
301 	char* sep = strchr(pathPtr, '/');
302 	if (sep != NULL)
303 		*sep = '\0';
304 
305 	if (strcmp(pathPtr, checkDir->ref.name) == 0) {
306 		// Root directory matches, so follow the remainder of the path as
307 		// far as possible.
308 		prefDir = checkDir;
309 		pathPtr += 1 + strlen(pathPtr);
310 		while (pathPtr < endOfPath) {
311 			sep = strchr(pathPtr, '/');
312 			if (sep != NULL)
313 				*sep = '\0';
314 
315 			checkDir = prefDir->FindChild(pathPtr);
316 			if (checkDir == NULL || checkDir->children.size() == 0)
317 				break;
318 
319 			pathPtr += 1 + strlen(pathPtr);
320 			prefDir = checkDir;
321 		}
322 	}
323 
324 	// If the desired path is the volume's root directory, default to the
325 	// volume display.
326 	if (prefDir == fSnapshot->rootDir)
327 		prefDir = NULL;
328 
329 	ChangeDir(prefDir);
330 
331 	free(workPath);
332 	fDesiredPath.erase();
333 }
334