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
24 using std::vector;
25
26
Scanner(BVolume * v,BHandler * handler)27 Scanner::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
~Scanner()44 Scanner::~Scanner()
45 {
46 delete fSnapshot;
47 }
48
49
50 void
MessageReceived(BMessage * message)51 Scanner::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
70 void
Refresh(FileInfo * startInfo)71 Scanner::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
91 void
Cancel()92 Scanner::Cancel()
93 {
94 if (!fBusy)
95 return;
96
97 fQuitRequested = true;
98 }
99
100
101 void
SetDesiredPath(string & path)102 Scanner::SetDesiredPath(string &path)
103 {
104 fDesiredPath = path;
105
106 if (fSnapshot == NULL)
107 Refresh();
108 else
109 _ChangeToDesired();
110 }
111
112
113 void
RequestQuit()114 Scanner::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
130 bool
_DirectoryContains(FileInfo * currentDir,entry_ref * ref)131 Scanner::_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
143 void
_RunScan(FileInfo * startInfo)144 Scanner::_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
228 FileInfo*
_GetFileInfo(BDirectory * dir,FileInfo * parent)229 Scanner::_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
287 void
_ChangeToDesired()288 Scanner::_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