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