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