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