xref: /haiku/src/apps/diskusage/Scanner.cpp (revision bf243977ffd34197ad7c0820c2da5d21abea0402)
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 <Directory.h>
17 #include <PathMonitor.h>
18 
19 #include "Common.h"
20 
21 using std::vector;
22 
23 
24 Scanner::Scanner(BVolume *v, BHandler *handler)
25 	:
26 	BLooper(),
27 	fListener(handler),
28 	fDoneMessage(kScanDone),
29 	fProgressMessage(kScanProgress),
30 	fVolume(v),
31 	fSnapshot(NULL),
32 	fDesiredPath(),
33 	fTask(),
34 	fBusy(false),
35 	fQuitRequested(false),
36 	fIsWatching(false),
37 	fModifiedEntries()
38 {
39 	Run();
40 }
41 
42 
43 Scanner::~Scanner()
44 {
45 	if (fIsWatching) {
46 		BPrivate::BPathMonitor::StopWatching(BMessenger(this, this));
47 		fIsWatching = false;
48 	}
49 	delete fSnapshot;
50 
51 	while (fModifiedEntries.size() != 0) {
52 		entry_ref* entry = *fModifiedEntries.begin();
53 		delete entry;
54 		fModifiedEntries.erase(fModifiedEntries.begin());
55 	}
56 }
57 
58 
59 void
60 Scanner::MessageReceived(BMessage* message)
61 {
62 	switch (message->what) {
63 		case kScanRefresh:
64 		{
65 			FileInfo* startInfo;
66 			if (message->FindPointer(kNameFilePtr, (void **)&startInfo) == B_OK)
67 				_RunScan(startInfo);
68 			break;
69 		}
70 
71 		case B_PATH_MONITOR:
72 		{
73 			dev_t device;
74 			ino_t directory;
75 			const char* name = "";
76 			const char* path;
77 
78 			if (((message->FindInt32("device", &device) != B_OK)
79 					|| (message->FindInt64("directory", &directory) != B_OK)
80 					|| (message->FindString("name", &name) != B_OK))
81 				&& ((message->FindString("path", &path) != B_OK)
82 					|| (message->FindInt32("device", &device) != B_OK)))
83 				return;
84 
85 			entry_ref* reportedEntry;
86 			if (strlen(name) > 0)
87 				reportedEntry = new entry_ref(device, directory, name);
88 			else {
89 				BEntry entry(path);
90 				reportedEntry = new entry_ref();
91 				entry.GetRef(reportedEntry);
92 			}
93 
94 			fModifiedEntries.push_back(reportedEntry);
95 
96 			if (IsOutdated()) {
97 				BMessage msg(kOutdatedMsg);
98 				fListener.SendMessage(&msg);
99 			}
100 
101 		}
102 
103 		default:
104 			BLooper::MessageReceived(message);
105 			break;
106 	}
107 }
108 
109 
110 void
111 Scanner::Refresh(FileInfo* startInfo)
112 {
113 	if (fBusy)
114 		return;
115 
116 	fBusy = true;
117 
118 	while (fModifiedEntries.size() != 0) {
119 		entry_ref* entry = *fModifiedEntries.begin();
120 		delete entry;
121 		fModifiedEntries.erase(fModifiedEntries.begin());
122 	}
123 
124 	// Remember the current directory, if any, so we can return to it after
125 	// the scanning is done.
126 	if (fSnapshot != NULL && fSnapshot->currentDir != NULL)
127 		fSnapshot->currentDir->GetPath(fDesiredPath);
128 
129 	fTask.assign(kEmptyStr);
130 
131 	BMessage msg(kScanRefresh);
132 	msg.AddPointer(kNameFilePtr, startInfo);
133 	PostMessage(&msg);
134 }
135 
136 
137 void
138 Scanner::SetDesiredPath(string &path)
139 {
140 	fDesiredPath = path;
141 
142 	if (fSnapshot == NULL)
143 		Refresh();
144 	else
145 		_ChangeToDesired();
146 }
147 
148 
149 void
150 Scanner::RequestQuit()
151 {
152 	// If the looper thread is scanning, we won't succeed to grab the lock,
153 	// since the thread is scanning from within MessageReceived(). Then by
154 	// setting fQuitRequested, the thread will stop scanning and only after
155 	// that happened we succeed to grab the lock. So this line of action
156 	// should be safe.
157 	fQuitRequested = true;
158 	Lock();
159 	Quit();
160 }
161 
162 
163 bool
164 Scanner::IsOutdated()
165 {
166 	FileInfo* currentDir = (fSnapshot->currentDir != NULL ?
167 		fSnapshot->currentDir : fSnapshot->rootDir);
168 
169 	BDirectory currentDirectory(&(currentDir->ref));
170 
171 	bool isOutdated = currentDirectory.InitCheck() != B_OK;
172 
173 	vector<entry_ref*>::iterator i = fModifiedEntries.begin();
174 	while (!isOutdated && i != fModifiedEntries.end()) {
175 		BEntry entry(*i);
176 		isOutdated = _DirectoryContains(currentDir, *i)
177 			|| currentDirectory.Contains(&entry);
178 		i++;
179 	}
180 	return isOutdated;
181 }
182 
183 
184 // #pragma mark - private
185 
186 
187 bool
188 Scanner::_DirectoryContains(FileInfo* currentDir, entry_ref* ref)
189 {
190 	vector<FileInfo*>::iterator i = currentDir->children.begin();
191 	bool contains = currentDir->ref == *ref;
192 	while (!contains && i != currentDir->children.end()) {
193 		contains |= _DirectoryContains((*i), ref);
194 		i++;
195 	}
196 	return contains;
197 }
198 
199 
200 void
201 Scanner::_RunScan(FileInfo* startInfo)
202 {
203 	if (startInfo == NULL || startInfo == fSnapshot->rootDir) {
204 		delete fSnapshot;
205 		fSnapshot = new VolumeSnapshot(fVolume);
206 		fTask = kStrScanningX + fSnapshot->name;
207 		fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes;
208 		fVolumeBytesScanned = 0;
209 		fProgress = 0.0;
210 		fLastReport = -100.0;
211 
212 		BDirectory root;
213 		fVolume->GetRootDirectory(&root);
214 		fSnapshot->rootDir = _GetFileInfo(&root, NULL);
215 
216 		FileInfo* freeSpace = new FileInfo;
217 		freeSpace->pseudo = true;
218 		string s = "Free on " + fSnapshot->name;
219 		freeSpace->ref.set_name(s.c_str());
220 		freeSpace->size = fSnapshot->freeBytes;
221 		fSnapshot->freeSpace = freeSpace;
222 
223 		fSnapshot->currentDir = NULL;
224 
225 	} else {
226 		fSnapshot->capacity = fVolume->Capacity();
227 		fSnapshot->freeBytes = fVolume->FreeBytes();
228 		fTask = kStrScanningX + string(startInfo->ref.name);
229 		fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes;
230 		fVolumeBytesScanned = fVolumeBytesInUse - startInfo->size; // best guess
231 		fProgress = 100.0 * fVolumeBytesScanned / fVolumeBytesInUse;
232 		fLastReport = -100.0;
233 
234 		BDirectory startDir(&startInfo->ref);
235 		if (startDir.InitCheck() == B_OK) {
236 			FileInfo *parent = startInfo->parent;
237 			vector<FileInfo *>::iterator i = parent->children.begin();
238 			FileInfo* newInfo = _GetFileInfo(&startDir, parent);
239 			while (i != parent->children.end() && *i != startInfo)
240 				i++;
241 
242 			int idx = i - parent->children.begin();
243 			parent->children[idx] = newInfo;
244 
245 			// Fixup count and size fields in parent directory.
246 			parent->size += newInfo->size - startInfo->size;
247 			parent->count += newInfo->count - startInfo->count;
248 
249 			delete startInfo;
250 		}
251 	}
252 
253 	if (!fIsWatching) {
254 		string path;
255 		fSnapshot->rootDir->GetPath(path);
256 
257 		BPrivate::BPathMonitor::StartWatching(path.c_str(),
258 			B_WATCH_ALL | B_WATCH_RECURSIVELY, BMessenger(this, this));
259 
260 		fIsWatching = true;
261 	}
262 
263 	fBusy = false;
264 	_ChangeToDesired();
265 	fListener.SendMessage(&fDoneMessage);
266 }
267 
268 
269 FileInfo*
270 Scanner::_GetFileInfo(BDirectory* dir, FileInfo* parent)
271 {
272 	FileInfo* thisDir = new FileInfo;
273 	BEntry entry;
274 	dir->GetEntry(&entry);
275 	entry.GetRef(&thisDir->ref);
276 	thisDir->parent = parent;
277 	thisDir->count = 0;
278 
279 	while (true) {
280 		if (fQuitRequested)
281 			break;
282 
283 		if (dir->GetNextEntry(&entry) == B_ENTRY_NOT_FOUND)
284 			break;
285 		if (entry.IsSymLink())
286 			continue;
287 
288 		if (entry.IsFile()) {
289 			FileInfo *child = new FileInfo;
290 			entry.GetRef(&child->ref);
291 			entry.GetSize(&child->size);
292 			child->parent = thisDir;
293 			child->color = -1;
294 			thisDir->children.push_back(child);
295 
296 			// Send a progress report periodically.
297 			fVolumeBytesScanned += child->size;
298 			fProgress = 100.0 * fVolumeBytesScanned / fVolumeBytesInUse;
299 			if (fProgress - fLastReport > kReportInterval) {
300 				fLastReport = fProgress;
301 				fListener.SendMessage(&fProgressMessage);
302 			}
303 		}
304 		else if (entry.IsDirectory()) {
305 			BDirectory childDir(&entry);
306 			thisDir->children.push_back(_GetFileInfo(&childDir, thisDir));
307 		}
308 		thisDir->count++;
309 	}
310 
311 	vector<FileInfo *>::iterator i = thisDir->children.begin();
312 	while (i != thisDir->children.end()) {
313 		thisDir->size += (*i)->size;
314 		thisDir->count += (*i)->count;
315 		i++;
316 	}
317 
318 	return thisDir;
319 }
320 
321 
322 void
323 Scanner::_ChangeToDesired()
324 {
325 	if (fBusy || fDesiredPath.size() <= 0)
326 		return;
327 
328 	char* workPath = strdup(fDesiredPath.c_str());
329 	char* pathPtr = &workPath[1]; // skip leading '/'
330 	char* endOfPath = pathPtr + strlen(pathPtr);
331 
332 	// Check the name of the root directory.  It is a special case because
333 	// it has no parent data structure.
334 	FileInfo* checkDir = fSnapshot->rootDir;
335 	FileInfo* prefDir = NULL;
336 	char* sep = strchr(pathPtr, '/');
337 	if (sep != NULL)
338 		*sep = '\0';
339 
340 	if (strcmp(pathPtr, checkDir->ref.name) == 0) {
341 		// Root directory matches, so follow the remainder of the path as
342 		// far as possible.
343 		prefDir = checkDir;
344 		pathPtr += 1 + strlen(pathPtr);
345 		while (pathPtr < endOfPath) {
346 			sep = strchr(pathPtr, '/');
347 			if (sep != NULL)
348 				*sep = '\0';
349 
350 			checkDir = prefDir->FindChild(pathPtr);
351 			if (checkDir == NULL || checkDir->children.size() == 0)
352 				break;
353 
354 			pathPtr += 1 + strlen(pathPtr);
355 			prefDir = checkDir;
356 		}
357 	}
358 
359 	// If the desired path is the volume's root directory, default to the
360 	// volume display.
361 	if (prefDir == fSnapshot->rootDir)
362 		prefDir = NULL;
363 
364 	ChangeDir(prefDir);
365 
366 	free(workPath);
367 	fDesiredPath.erase();
368 }
369