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	fPreviousSnapshot(NULL)
40{
41	Run();
42}
43
44
45Scanner::~Scanner()
46{
47	delete fSnapshot;
48}
49
50
51void
52Scanner::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
71void
72Scanner::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
92void
93Scanner::Cancel()
94{
95	if (!fBusy)
96		return;
97
98	fQuitRequested = true;
99}
100
101
102void
103Scanner::SetDesiredPath(string &path)
104{
105	fDesiredPath = path;
106
107	if (fSnapshot == NULL)
108		Refresh();
109	else
110		_ChangeToDesired();
111}
112
113
114void
115Scanner::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
131bool
132Scanner::_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
144void
145Scanner::_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
222FileInfo*
223Scanner::_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
275void
276Scanner::_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