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