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