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