/* * Copyright (c) 2008 Stephan Aßmus . All rights reserved. * Distributed under the terms of the MIT/X11 license. * * Copyright (c) 1999 Mike Steed. You are free to use and distribute this software * as long as it is accompanied by it's documentation and this copyright notice. * The software comes with no warranty, etc. */ #include "Scanner.h" #include #include #include #include #include "DiskUsage.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Scanner" using std::vector; Scanner::Scanner(BVolume *v, BHandler *handler) : BLooper(), fListener(handler), fDoneMessage(kScanDone), fProgressMessage(kScanProgress), fVolume(v), fSnapshot(NULL), fDesiredPath(), fTask(), fBusy(false), fQuitRequested(false) { Run(); } Scanner::~Scanner() { delete fSnapshot; } void Scanner::MessageReceived(BMessage* message) { switch (message->what) { case kScanRefresh: { FileInfo* startInfo; if (message->FindPointer(kNameFilePtr, (void **)&startInfo) == B_OK) _RunScan(startInfo); break; } default: BLooper::MessageReceived(message); break; } } void Scanner::Refresh(FileInfo* startInfo) { if (fBusy) return; fBusy = true; // Remember the current directory, if any, so we can return to it after // the scanning is done. if (fSnapshot != NULL && fSnapshot->currentDir != NULL) fSnapshot->currentDir->GetPath(fDesiredPath); fTask.assign(kEmptyStr); BMessage msg(kScanRefresh); msg.AddPointer(kNameFilePtr, startInfo); PostMessage(&msg); } void Scanner::Cancel() { if (!fBusy) return; fQuitRequested = true; } void Scanner::SetDesiredPath(string &path) { fDesiredPath = path; if (fSnapshot == NULL) Refresh(); else _ChangeToDesired(); } void Scanner::RequestQuit() { // If the looper thread is scanning, we won't succeed to grab the lock, // since the thread is scanning from within MessageReceived(). Then by // setting fQuitRequested, the thread will stop scanning and only after // that happened we succeed to grab the lock. So this line of action // should be safe. fQuitRequested = true; Lock(); Quit(); } // #pragma mark - private bool Scanner::_DirectoryContains(FileInfo* currentDir, entry_ref* ref) { vector::iterator i = currentDir->children.begin(); bool contains = currentDir->ref == *ref; while (!contains && i != currentDir->children.end()) { contains |= _DirectoryContains((*i), ref); i++; } return contains; } void Scanner::_RunScan(FileInfo* startInfo) { fQuitRequested = false; BString stringScan(B_TRANSLATE("Scanning %refName%")); if (startInfo == NULL || startInfo == fSnapshot->rootDir) { VolumeSnapshot* previousSnapshot = fSnapshot; fSnapshot = new VolumeSnapshot(fVolume); stringScan.ReplaceFirst("%refName%", fSnapshot->name.c_str()); fTask = stringScan.String(); fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes; fVolumeBytesScanned = 0; fProgress = 0.0; fLastReport = -1.0; BDirectory root; fVolume->GetRootDirectory(&root); fSnapshot->rootDir = _GetFileInfo(&root, NULL); if (fSnapshot->rootDir == NULL) { delete fSnapshot; fSnapshot = previousSnapshot; fBusy = false; fListener.SendMessage(&fDoneMessage); return; } FileInfo* freeSpace = new FileInfo; freeSpace->pseudo = true; BString string(B_TRANSLATE("Free on %refName%")); string.ReplaceFirst("%refName%", fSnapshot->name.c_str()); freeSpace->ref.set_name(string.String()); freeSpace->size = fSnapshot->freeBytes; fSnapshot->freeSpace = freeSpace; fSnapshot->currentDir = NULL; delete previousSnapshot; } else { off_t previousVolumeCapacity = fSnapshot->capacity; off_t previousVolumeFreeBytes = fSnapshot->freeBytes; fSnapshot->capacity = fVolume->Capacity(); fSnapshot->freeBytes = fVolume->FreeBytes(); stringScan.ReplaceFirst("%refName%", startInfo->ref.name); fTask = stringScan.String(); fVolumeBytesInUse = fSnapshot->capacity - fSnapshot->freeBytes; fVolumeBytesScanned = fVolumeBytesInUse - startInfo->size; //best guess fProgress = fVolumeBytesScanned / fVolumeBytesInUse; fLastReport = -1.0; BDirectory startDir(&startInfo->ref); if (startDir.InitCheck() == B_OK) { FileInfo *parent = startInfo->parent; vector::iterator i = parent->children.begin(); FileInfo* newInfo = _GetFileInfo(&startDir, parent); if (newInfo == NULL) { fSnapshot->capacity = previousVolumeCapacity; fSnapshot->freeBytes = previousVolumeFreeBytes; fBusy = false; fListener.SendMessage(&fDoneMessage); return; } while (i != parent->children.end() && *i != startInfo) i++; int idx = i - parent->children.begin(); parent->children[idx] = newInfo; // Fixup count and size fields in parent directory. off_t sizeDiff = newInfo->size - startInfo->size; off_t countDiff = newInfo->count - startInfo->count; while (parent != NULL) { parent->size += sizeDiff; parent->count += countDiff; parent = parent->parent; } delete startInfo; } } fBusy = false; _ChangeToDesired(); fListener.SendMessage(&fDoneMessage); } FileInfo* Scanner::_GetFileInfo(BDirectory* dir, FileInfo* parent) { FileInfo* thisDir = new FileInfo; BEntry entry; dir->GetEntry(&entry); entry.GetRef(&thisDir->ref); thisDir->parent = parent; thisDir->count = 0; while (true) { if (fQuitRequested) { delete thisDir; return NULL; } if (dir->GetNextEntry(&entry) == B_ENTRY_NOT_FOUND) break; if (entry.IsSymLink()) continue; if (entry.IsFile()) { entry_ref ref; if ((entry.GetRef(&ref) == B_OK) && (ref.device != Device())) continue; FileInfo *child = new FileInfo; entry.GetRef(&child->ref); entry.GetSize(&child->size); child->parent = thisDir; child->color = -1; thisDir->children.push_back(child); // Send a progress report periodically. fVolumeBytesScanned += child->size; fProgress = (float)fVolumeBytesScanned / fVolumeBytesInUse; if (fProgress - fLastReport > kReportInterval) { fLastReport = fProgress; fListener.SendMessage(&fProgressMessage); } } else if (entry.IsDirectory()) { BDirectory childDir(&entry); thisDir->children.push_back(_GetFileInfo(&childDir, thisDir)); } thisDir->count++; } vector::iterator i = thisDir->children.begin(); while (i != thisDir->children.end()) { thisDir->size += (*i)->size; thisDir->count += (*i)->count; i++; } return thisDir; } void Scanner::_ChangeToDesired() { if (fBusy || fDesiredPath.size() <= 0) return; char* workPath = strdup(fDesiredPath.c_str()); char* pathPtr = &workPath[1]; // skip leading '/' char* endOfPath = pathPtr + strlen(pathPtr); // Check the name of the root directory. It is a special case because // it has no parent data structure. FileInfo* checkDir = fSnapshot->rootDir; FileInfo* prefDir = NULL; char* sep = strchr(pathPtr, '/'); if (sep != NULL) *sep = '\0'; if (strcmp(pathPtr, checkDir->ref.name) == 0) { // Root directory matches, so follow the remainder of the path as // far as possible. prefDir = checkDir; pathPtr += 1 + strlen(pathPtr); while (pathPtr < endOfPath) { sep = strchr(pathPtr, '/'); if (sep != NULL) *sep = '\0'; checkDir = prefDir->FindChild(pathPtr); if (checkDir == NULL || checkDir->children.size() == 0) break; pathPtr += 1 + strlen(pathPtr); prefDir = checkDir; } } // If the desired path is the volume's root directory, default to the // volume display. if (prefDir == fSnapshot->rootDir) prefDir = NULL; ChangeDir(prefDir); free(workPath); fDesiredPath.erase(); }