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