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